Skip to content

eudeka/statistic_covid19

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Eudeka! Aplikasi Covid19

Persiapan

  • Laptop
  • Internet
  • Sudah terinstall
    • Flutter SDK
    • Android SDK
    • iOS SDK (opsional - untuk perangkat iPhone/iPad)
  • Dasar logika yang kuat
  • Tekad belajar yang kuat

TODO 00

Buat project Flutter baru.

TODO 01

Tambahkan library (dependencies) http, yang akan berfungsi untuk memanggil REST API atau mendapatkan data yang dibutuhkan dari internet.

Tambahkan library (dependencies) charts_flutter, yang akan berfungsi untuk menampilkan data yang ada menjadi sebuah grafik dengan berbentuk pie chart, bar chart, line, dan lainnya.

TODO 02

(Opsional) Hapus semua kode yang ada pada file widget_test.dart pada folder test, dan ganti dengan fungsi main saja seperti berikut,

void main() {}

TODO 03

Buat folder atau package baru di dalam folder lib dengan nama config, folder ini nantinya akan digunakan untuk menyimpan file konfigurasi seperti url utama untuk REST API, nama rute halaman, dan lain sebagainya.

Buat file dart baru pada folder config dengan nama env.dart, isi dengan kelas Env dan beberapa nilai konstan seperti, baseUrl adalah berisi url utama untuk pemanggilan API nantinya, homeRoute adalah nama rute untuk halaman home, dan detailRoute adalah nama rute untuk halaman detail. Sedangkan ChartsType adalah sebuah enum yang akan kita gunakan untuk mengganti tampilan dari bar charts ke pie charts.

class Env {
  static final String baseUrl = "https://indonesia-covid-19.mathdro.id/api";
  static final String homeRoute = "/home";
  static final String detailRoute = "/detail";
}

enum ChartsType { BAR, PIE }

TODO 04

Buat folder atau package model pada folder lib, folder ini nantinya digunakan untuk menyimpan data model yang akan digunakan untuk menyimpan nilai dari data REST API yang didapatkan.

Buka url REST API berikut ini pada browser dan copy semua kode yang muncul, buka pada tab baru aplikasi Quicktype dan paste kode yang tadi pada bagian kiri, jangan lupa untuk mengganti nama kelasnya dan bahasanya menjadi Dart.

(Opsional) Kamu juga bisa mencentang beberapa opsi yang ada pada Quicktype tadi sesuai dengan kebutuhan.

Copy kode yang ada pada Quicktype bagian kanan, lalu buat file dart baru pada folder model dengan nama corona_province.dart dan paste kode tadi ke dalam file tersebut.

TODO 05

Buat juga file dart dengan nama corona_cases.dart pada folder model dan isi dengan sebuah kelas model CoronaCases untuk menyimpan title dan count, yang akan digunakan untuk model data pada grafik (charts) nantinya

class CoronaCases {
  String title;
  int count;

  CoronaCases({
    this.title,
    this.count,
  });
}

TODO 06

Buat folder atau package baru dengan nama network pada folder lib, folder ini nantinya digunakan untuk menyimpan file dart untuk koneksi ke internet.

Buat file dart baru pada folder network dengan nama api_client.dart, dan isi dengan fungsi untuk mendapatkan data dari internet (REST API). Buat fungsi tersebut menjadi static agar fungsi tersebut dapat diakses langsung dari kelas (ApiClient.getCoronaProvince), bukan ketika instance kelas (ApiClient().getCoronaProvince), untuk lebih detailnya bisa coba baca disini. Lalu gunakan tipe data Future dengan nilai T nya adalah kelas data model yang sesuai dengan JSON yang nanti didapatkan, tipe Future ini adalah tipe yang bersifat asynchronous atau nilai akan terisi setelah semua perintah di dalam fungsi dijalankan (tidak berurutan sesuai baris). Kemudian Response adalah berisi nilai hasil dari pemanggilan url https://indonesia-covid-19.mathdro.id/api/provinsi dengan method GET, lalu lakukan pengecekan jika status code dari response tersebut bernilai 200 atau artinya data berhasil didapatkan maka berikan nilai balik dari fungsi tersebut CoronaProvince.fromJson yang diambil dari kelas model dari nilai T fungsi tersebut, dan jika bukan 200 atau gagal maka lempar Exception error code tersebut.

class ApiClient {
  static Future<CoronaProvince> getCoronaProvince() async {
    Response _response = await get("${Env.baseUrl}/provinsi");
    if (_response.statusCode == 200) {
      return CoronaProvince.fromJson(_response.body);
    } else {
      throw Exception("error code : ${_response.statusCode}");
    }
  }
}

TODO 07

Buat folder dengan nama view di dalam folder lib dan dua folder lagi di dalam folder view yaitu detail dan home, folder ini nantinya digunakan untuk menyimpan file dart untuk UI atau tampilan pada aplikasi.

Tambahkan di masing-masing folder tersebut dua file dart, home_page.dart dan home_content.dart pada folder home, sedangkan detail_page.dart dan detail_content.dart pada folder detail.

Pada file home_page.dart buat kelas HomePage dengan StatelessWidget, dan isi fungsi build dengan Scaffold, seperti berikut,

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Container(
        alignment: Alignment.center,
      ),
    );
  }
}

TODO 08

Pada file home_content.dart, buat kelas HomeContent dengan StatefulWidget, tambahkan juga parameter CoronaProvince atau kelas model yang sebelumnya dibuat pada kelas tersebut.

class HomeContent extends StatefulWidget {
  final CoronaProvince coronaProvinceId;

  const HomeContent({
    Key key,
    this.coronaProvinceId,
  }) : super(key: key);

  @override
  _HomeContentState createState() => _HomeContentState();
}

class _HomeContentState extends State<HomeContent> {
  @override
  Widget build(BuildContext context) {
    return Container();
  }
}

Deklarasikan dua List yang akan digunakan untuk menampilkan data nantinya, dan inisialisasikan datanya pada fungsi initState. Dimana data pertama (_listCoronaCases) adalah list untuk data keseluruhan (total) positif, sembuh, meninggal, dan perawatan, sedangkan data kedua (_listData) adalah list data asli yang didapatkan dari REST API yang masih berupa data per provinsi. Dan untuk mendapatkan data keseluruhan (total) untuk positif, sembuh, dan meninggal, gunakan looping for in dari data per provinsi kemudian tambahkan nilainya ke dalam data keseluruhan, sedangkan untuk data perawatan didapatkan dari total positif dikurangi total sembuh dan meninggal.

[...]

List<CoronaCases> _listCoronaCases;
List<Datum> _listData;
  
@override
void initState() {
  _listCoronaCases = <CoronaCases>[
    CoronaCases(
      title: "Positif",
      count: 0,
    ),
    CoronaCases(
      title: "Sembuh",
      count: 0,
    ),
    CoronaCases(
      title: "Meninggal",
      count: 0,
    ),
    CoronaCases(
      title: "Perawatan",
      count: 0,
    ),
  ];
  _listData = widget.coronaProvinceId.listData;
  for (Datum data in _listData) {
    _listCoronaCases[0].count += data.positiveCases;
    _listCoronaCases[1].count += data.curedCases;
    _listCoronaCases[2].count += data.deathCases;
  }
  _listCoronaCases[3].count = _listCoronaCases[0].count;
  _listCoronaCases[3].count -= _listCoronaCases[1].count;
  _listCoronaCases[3].count -= _listCoronaCases[2].count;
  super.initState();
}

[...]

TODO 09

Buat sebuah fungsi widget di dalam kelas _HomeContentState, yang nantinya digunakan untuk item yang menampilkan data keseluruhan (total), berikan pula parameter untuk mendapatkan index dari item tersebut. Gunakan widget ListView untuk menghindari error overflowed ketika ditampilkan pada layar yang terlalu kecil atau teks yang berlebih, berikan primary dengan false agar tidak dapat di scroll ketika ukuran mencukupi untuk ditampilkan, dan shrinkWrap dengan true agar ukuran mengikuti ukuran item di dalamnya.

[...]

Widget _itemGrid(int index) {
  return Card(
    elevation: 8.0,
    margin: const EdgeInsets.all(16.0),
    color: Colors.primaries[index],
    clipBehavior: Clip.antiAliasWithSaveLayer,
    child: Center(
      child: ListView(
        primary: false,
        shrinkWrap: true,
        children: <Widget>[
          Text(
            "${_listCoronaCases[index].title}",
            style: TextStyle(
              fontSize: 32.0,
            ),
            textAlign: TextAlign.center,
            overflow: TextOverflow.ellipsis,
            maxLines: 1,
          ),
          Text(
            "${_listCoronaCases[index].count}",
            style: TextStyle(
              fontSize: 64.0,
            ),
            textAlign: TextAlign.center,
            overflow: TextOverflow.ellipsis,
            maxLines: 1,
          ),
        ],
      ),
    ),
  );
}

[...]

TODO 10

Pada fungsi build kelas _HomeContentState buat agar dapat memunculkan GridView dan ListView bersamaan di dalam Column dan bungkus kembali dengan SingleChildScrollView agar dapat digulirkan. Jangan lupa untuk atur primary menjadi false dan shrinkWrap menjadi true pada GridView dan ListView agar keduanya tidak dapat digulirkan dan mengikuti ukuran item masing-masing, karena sudah menggunakan SingleChildScrollView untuk dapat digulirkan.

[...]

@override
Widget build(BuildContext context) {
  return SingleChildScrollView(
    child: Column(
      children: <Widget>[
        GridView.builder(
          primary: false,
          shrinkWrap: true,
          padding: const EdgeInsets.all(16.0),
          gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
            maxCrossAxisExtent: 512.0,
            childAspectRatio: 1.5,
          ),
          itemBuilder: (BuildContext context, int index) {
            return _itemGrid(index);
          },
          itemCount: _listCoronaCases.length,
        ),
        ListView.builder(
          primary: false,
          shrinkWrap: true,
          itemBuilder: (BuildContext context, int index) {
            return Card(
              child: ListTile(
                title: Text(_listData[index].province),
                trailing: Icon(Icons.chevron_right),
              ),
            );
          },
          itemCount: _listData.length,
        ),
      ],
    ),
  );
}

[...]

TODO 11

Pada file detail_page.dart buat kelas DetailPage dengan StatefulWidget untuk halaman detail.

class DetailPage extends StatefulWidget {
  @override
  _DetailPageState createState() => _DetailPageState();
}

class _DetailPageState extends State<DetailPage> {
  @override
  Widget build(BuildContext context) {
    return Container();
  }
}

Pada kelas _DetailPageState inisialisasikan _chartsType dengan nilai ChartsType.BAR untuk membuat tampilan default adalah berbentuk charts bar. Pada fungsi build tangkap argumen yang dilempar dari halaman sebelumnya dan masukan ke dalam _data. Buat juga IconButton pada actions di AppBar, yang dimana ketika di klik ubah nilai _chartsType, jika _chartsType sebelumnya adalah BAR maka isi dengan PIE atau jika bukan isi dengan BAR, jangan lupa untuk gunakan setState setelahnya untuk trigger perubahan tampilan.

[...]

ChartsType _chartsType;

@override
void initState() {
  _chartsType = ChartsType.BAR;
  super.initState();
}

@override
Widget build(BuildContext context) {
  Datum _data = ModalRoute.of(context).settings.arguments;
  return Scaffold(
    appBar: AppBar(
      title: Text(_data?.province ?? ""),
      actions: <Widget>[
        IconButton(
          icon: Icon(Icons.cached),
          onPressed: () {
            _chartsType = _chartsType == ChartsType.BAR
              ? ChartsType.PIE
              : ChartsType.BAR;
            setState(() {});
          },
        ),
      ],
    ),
    body: Card(
      margin: const EdgeInsets.all(16.0),
      child: Container(
        alignment: Alignment.center,
        padding: const EdgeInsets.all(16.0),
      ),
    ),
  );
}

[...]

TODO 12

Pada file detail_content.dart buat kelas baru dengan nama DetailContent. Berikan parameter data yaitu data yang akan dimunculkan pada chats dan chartsType yaitu tipe charts yang dimunculkan.

class DetailContent extends StatefulWidget {
  final Datum data;
  final ChartsType chartsType;

  const DetailContent({
    Key key,
    this.data,
    this.chartsType,
  }) : super(key: key);

  @override
  _DetailContentState createState() => _DetailContentState();
}

class _DetailContentState extends State<DetailContent> {
  @override
  Widget build(BuildContext context) {
    return Container();
  }
}

TODO 13

Pada kelas _DetailContentState inisialisasikan _listCases dan _listSeries, dimana _listCases adalah data yang akan kita tampilkan pada charts untuk satuannya, disini kita coba untuk munculkan data positif, sembuh, dan meninggal, sedangkan _listSeries adalah kelompok data yang dibutuhkan untuk digunakan pada charts baik pada pie maupun bar, disini kita cukup gunakan satu kelopok atau satu Series, dengan id-nya adalah nama provinsi, lalu data yang gunakan adalah _listCases, domainFn adalah untuk judul setiap datanya (berbentuk String), measureFn adalah data atau angka yang akan diukur (berbentuk int), dan labelAccessorFn adalah untuk label setiap data (berbentuk String).

[...]

List<CoronaCases> _listCases;
List<Series<CoronaCases, String>> _listSeries;

@override
void initState() {
  _listCases = <CoronaCases>[
    CoronaCases(
      title: "Positif",
      count: widget.data.positiveCases,
    ),
    CoronaCases(
      title: "Sembuh",
      count: widget.data.curedCases,
    ),
    CoronaCases(
      title: "Meninggal",
      count: widget.data.deathCases,
    ),
  ];
  _listSeries = <Series<CoronaCases, String>>[
    Series(
      id: widget.data.province,
      data: _listCases,
      domainFn: (CoronaCases cases, int index) {
        return cases.title;
      },
      measureFn: (CoronaCases cases, int index) {
        return cases.count;
      },
      labelAccessorFn: (CoronaCases cases, int index) {
        return "${cases.count}";
      },
    ),
  ];
  super.initState();
}

[...]

TODO 14

Pada fungsi build di _DetailContentState buat jika chartsType adalah BAR maka tampikan BarCharts dan jika bukan maka tampilkan PieCharts. Pada BarCharts, isi barRendererDecorator untuk menampikan label di setiap data, dan atur posisi label selalu berada pada luar bar. Pada PieCharts, isi defaultRenderer untuk menampilkan label, dan atur posisi label selalu berada pada luar pie, tambahkan juga DatumLegend pada behaviors untuk memunculkan legenda pada charts tersebut.

[...]

@override
Widget build(BuildContext context) {
  return widget.chartsType == ChartsType.BAR
      ? BarChart(
          _listSeries,
          barRendererDecorator: BarLabelDecorator<String>(
            labelPosition: BarLabelPosition.outside,
          ),
        )
      : PieChart(
          _listSeries,
          defaultRenderer: ArcRendererConfig(
            arcRendererDecorators: [
              ArcLabelDecorator(
                labelPosition: ArcLabelPosition.outside,
              ),
            ],
          ),
          behaviors: [
            DatumLegend(
              showMeasures: true,
              legendDefaultMeasure: LegendDefaultMeasure.sum,
            ),
          ],
        );
}

[...]

TODO 15

Balik lagi ke DetailPage, pada Container yang ada di body isi child nya dengan DetailContent dengan data parameter yang ada.

[...]
body: Card(
  [...]
  child: Container(
    [...]
    child: DetailContent(
      data: _data,
      chartsType: _chartsType,
    ),
  ),
),
[...]

TODO 16

Balik ke HomeContent, tambahkan onTap pada ListTile yang ada pada ListView. Dimana ketika di klik maka akan berpindah ke halaman DetailPage dengan melempar argumen yaitu sebuah data.

[...]
ListView.builder(
  [...]
  itemBuilder: (BuildContext context, int index) {
    return Card(
      child: ListTile(
        [...]
        onTap: () {
          Navigator.pushNamed(
            context,
            Env.detailRoute,
            arguments: _listData[index],
          );
        },
      ),
    );
  },
  [...]
),
[...]

TODO 17

Pada HomePage gunakan FutureBuilder pada body untuk mendapatkan dan menampilkan data dari REST API. Pada parameter future isi dengan fungsi yang mendapatkan data dari internet, dengan nilai T dari FutureBuilder adalah nilai balik dari fungsi yang ada pada future tersebut, begitu juga nilai T pada AsyncSnapshot. Pada builder lakukan pengecekan jika snapshot sudah memiliki data, maka tampilkan HomeContent dengan parameter data dari snapshot tersebut, namun jika snapshot masih kosong tampilkan CircularProgressIndicator.

[...]

@override
Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(
      title: Text("Eudeka! Flutter x Corona"),
    ),
    body: Container(
      alignment: Alignment.center,
      child: FutureBuilder<CoronaProvince>(
        future: ApiClient.getCoronaProvinceId(),
        builder: (
          BuildContext context,
          AsyncSnapshot<CoronaProvince> snapshot,
        ) {
          if (snapshot.hasData) {
            return HomeContent(
              coronaProvinceId: snapshot.data,
            );
          } else {
            return CircularProgressIndicator();
          }
        },
      ),
    ),
  );
}

[...]

TODO 18

Pada file main.dart ganti semua kode nya dengan kelas MainApp, dimana pada kelas tersebut buat sebuah _routes atau halaman-halaman yang ada pada aplikasi tersebut, karena kelas tersebut menggunakan StatelessWidget maka variabel yang ada kita buat menjadi final, dan isi initialRoute dengan halaman HomePage.

void main() {
  runApp(
    MainApp(),
  );
}

class MainApp extends StatelessWidget {
  final Map<String, WidgetBuilder> _routes = <String, WidgetBuilder>{
    Env.homeRoute: (BuildContext context) {
      return HomePage();
    },
    Env.detailRoute: (BuildContext context) {
      return DetailPage();
    },
  };

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      routes: _routes,
      initialRoute: Env.homeRoute,
    );
  }
}

Notes:
Jangan lupa untuk menambahkan izin akses internet pada AndroidManifest.xml ketika ingin menjalankan atau membuat aplikasi dalam mode release.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published