Membuat aplikasi informasi covid19 dengan arsitektur MVVM

Pada tutorial android kali ini, saya akan membagikan langkah-langkah bagaimana membuat aplikasi android dengan arsitektur MVVM.

Karena hari-hari sekarang sedang ada wabah corona dan beberapa aktivitas terganggu seperti kegiatan kantor, sekolah dll, ada baiknya kita gunakan untuk sesuatu yang bermanfaat salah satunya dengan belajar pemrograman. Langsung saja tanpa basa-basi cekidot.

Pembukaan

Dalam tutorial ini saya mengambil data dari situs https://api.kawalcorona.com/indonesia/provinsi. Aplikasi ini menggunakan arsitektur MVVM + Recyclerview untuk menampung data, LiveData sebagai komponen.

MVVM (Model – View – viewModel) adalah arsitektur yang direkomendasikan google. Mengapa menggunakan MVVM ? alasannya karena kode lebih rapih dan mudah untuk merawat.

Apa itu LiveData ? LiveData adalah class penyimpanan data observable. Tidak seperti observable pada umumnya, LiveData berbasis siklus hidup, yang berarti observable ini mengikuti siklus hidup komponen aplikasi lainnya, seperti activity, fragment, atau service

Selebihnya pengertian tentang MVVM, Recyclerview, Retrofit silahkan kalian baca di google.

Baca juga  Instalasi Node.js 12 di Ubuntu / Debian / Linux Mint

Baca juga  Membuat endless scrolling pada recyclerview
Baca juga  Kombinasi Spring Boot dengan DataTables

Demo Aplikasi

Seperti yang sudah saya sampaikan, aplikasi ini bersifat pembelajaran saja (mungkin nanti diperbaharui). Tampilannya pun ya biasa-biasa saja.

Struktur project

1. Tambahkan dependencies

dependencies {
    def lifecycle_version = "2.2.0"
    implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.0.0"
    //lifecycle
    implementation "android.arch.lifecycle:extensions:1.1.1"
    implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"
    // retrofit
    implementation "com.squareup.retrofit2:retrofit:2.8.1"
    implementation 'com.squareup.retrofit2:converter-gson:2.5.0'
    implementation 'androidx.recyclerview:recyclerview:1.1.0'
    
    implementation 'androidx.cardview:cardview:1.0.0'
}

Jangan lupa untuk meng-enable kan data binding, karena dalam tutorial ini kita akan gunakan data binding tambahkan di dalam / di bawah tag android.

dataBinding {
    enabled = true
}

2. Tambahkan kode di activity_main.xml

Buka activity_main.xml dan tambahkan kode berikut. Layout ini berisi RecyclerView dan Swiperefresh di dalam ConstraintLayout. Taruh kode tata letak di dalam tag <layout>, untuk mendukung data binding.

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    >
    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity"
        tools:showIn="@layout/activity_main">

        <androidx.swiperefreshlayout.widget.SwipeRefreshLayout
            android:id="@+id/swipeRefresh"
            android:layout_width="0dp"
            android:layout_height="0dp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent">

        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/viewCorona"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
        </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

3. Model

Sebelum membuat kode untuk model di android studio, coba perhatikan data yang disajikan oleh kawalcorona, datanya hanya berbentuk JSONObject.

attributes: {
    FID: 11,
    Kode_Provi: 31,
    Provinsi: "DKI Jakarta",
    Kasus_Posi: 675,
    Kasus_Semb: 45,
    Kasus_Meni: 68
}

Maka untuk kode modelnya jadi seperti ini :

import com.google.gson.annotations.SerializedName;

public class Corona {

    private Attributes attributes;

    public Attributes getAttributes() {
        return attributes;
    }

    public static class Attributes{
        @SerializedName("Provinsi")
        private String provinsi;
        @SerializedName("Kasus_Posi")
        private String positif;
        @SerializedName("Kasus_Semb")
        private String sembuh;
        @SerializedName("Kasus_Meni")
        private String meninggal;

        public String getProvinsi() {
            return provinsi;
        }

        public void setProvinsi(String provinsi) {
            this.provinsi = provinsi;
        }

        public String getPositif() {
            return positif;
        }

        public void setPositif(String positif) {
            this.positif = positif;
        }

        public String getSembuh() {
            return sembuh;
        }

        public void setSembuh(String sembuh) {
            this.sembuh = sembuh;
        }

        public String getMeninggal() {
            return meninggal;
        }

        public void setMeninggal(String meninggal) {
            this.meninggal = meninggal;
        }
    }
}

4. Desain list 

Untuk menampilkan daftar kasus covid19, kita membutuhkan cardview dan di dalamnya kita gunakan ConstraintLayout. Kita hanya ambil 4 data yaitu provinsi, positif, sembuh dan meninggal. Sebelumnya tambahkan font biar tampilan aplikasinya gak terlalu jelek. Font ditaruh di folder res.

Tambahkan custom_font.xml di dalam folder font.

<?xml version="1.0" encoding="utf-8"?>
<font-family xmlns:android="http://schemas.android.com/apk/res/android">

    <font
        android:font="@font/m_medium"
        android:fontStyle="normal"/>

    <font
        android:font="@font/m_regular"
        android:fontStyle="normal"/>

    <font
        android:font="@font/m_light"
        android:fontStyle="normal"/>
</font-family>

Buat file dengan nama corona_list_item.xml.

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:bind="http://schemas.android.com/apk/res-auto"
    xmlns:card_view="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">
    <data>
        <variable
            name="corona"
            type="com.dekikurnia.belajarmvvm.model.Corona"/>
    </data>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:adjustViewBounds="true">
        <androidx.cardview.widget.CardView
            android:id="@+id/cvCorona"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_gravity="center"
            android:layout_margin="8dp"
            android:elevation="3dp"
            card_view:cardCornerRadius="1dp">

            <androidx.constraintlayout.widget.ConstraintLayout
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:padding="4dp">

                <TextView
                    android:id="@+id/tvProvinsi"
                    android:layout_width="0dp"
                    android:layout_height="wrap_content"
                    android:layout_marginStart="8dp"
                    android:layout_marginjustify="8dp"
                    android:layout_marginTop="2dp"
                    android:layout_marginEnd="8dp"
                    android:padding="4dp"
                    android:text="@{`Provinsi : ` + corona.attributes.provinsi}"
                    android:textColor="@color/colorProvinsi"
                    android:textSize="20sp"
                    android:fontFamily="@font/m_medium"
                    bind:layout_constraintEnd_toEndOf="parent"
                    bind:layout_constraintStart_toStartOf="parent"
                    bind:layout_constraintTop_toTopOf="parent"
                    tools:text="DKI. Jakarta" />

                <TextView
                    android:id="@+id/tvPositif"
                    android:layout_width="0dp"
                    android:layout_height="wrap_content"
                    android:layout_marginStart="8dp"
                    android:layout_marginjustify="8dp"
                    android:padding="4dp"
                    android:text="@{`Positif : ` + corona.attributes.positif}"
                    android:textColor="@color/colorPositif"
                    android:textSize="14sp"
                    android:fontFamily="@font/m_regular"
                    bind:layout_constraintEnd_toEndOf="parent"
                    bind:layout_constraintStart_toStartOf="parent"
                    bind:layout_constraintTop_toBottomOf="@+id/tvProvinsi"
                    tools:text="Positif" />

                <TextView
                    android:id="@+id/tvSembuh"
                    android:layout_width="0dp"
                    android:layout_height="wrap_content"
                    android:layout_marginStart="8dp"
                    android:layout_marginjustify="8dp"
                    android:padding="4dp"
                    android:text="@{`Sembuh : ` + corona.attributes.sembuh}"
                    android:textColor="@color/colorSembuh"
                    android:textSize="14sp"
                    android:fontFamily="@font/m_regular"
                    bind:layout_constraintEnd_toEndOf="parent"
                    bind:layout_constraintStart_toStartOf="parent"
                    bind:layout_constraintTop_toBottomOf="@+id/tvPositif"
                    tools:text="Sembuh" />

                <TextView
                    android:id="@+id/tvMeninggal"
                    android:layout_width="0dp"
                    android:layout_height="wrap_content"
                    android:layout_marginStart="8dp"
                    android:layout_marginjustify="8dp"
                    android:padding="4dp"
                    android:text="@{`Meninggal : ` + corona.attributes.meninggal}"
                    android:textColor="@color/colorMeninggal"
                    android:textSize="14sp"
                    android:fontFamily="@font/m_regular"
                    bind:layout_constraintEnd_toEndOf="parent"
                    bind:layout_constraintStart_toStartOf="parent"
                    bind:layout_constraintTop_toBottomOf="@+id/tvSembuh"
                    tools:text="Meninggal" />
            </androidx.constraintlayout.widget.ConstraintLayout>
        </androidx.cardview.widget.CardView>
    </LinearLayout>
</layout>

Jika anda perhatikan, di dalam layout tersebut ada tag <variabel>. Di dalam tag tersebut kita deklarasikan objek corona, jadi kita set seperti ini untuk mengambil value ke dalam TextView : corona.attributes.meninggal

5. Implementasi adapter

Adapter ini digunakan untuk merepresentasikan RecyclerView sekaligus untuk data binding model data ke dalam layout.

Hal ini dilakukan dengan menetapkan penampung tampilan ke sebuah posisi, lalu memanggil metode onBindViewHolder(). Metode tersebut menggunakan posisi penampung tampilan untuk menentukan apa kontennya, berdasarkan posisi daftarnya.

Penjelasan lengkapnya silahkan lihat link ini https://developer.android.com/guide/topics/ui/layout/recyclerview?hl=id

public class CoronaDataAdapter extends RecyclerView.Adapter<CoronaDataAdapter.CoronaViewHolder> {
    private ArrayList<Corona> corona;

    public CoronaDataAdapter(List<Corona> coronaList) {
    }

    @NonNull
    @Override
    public CoronaViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
        CoronaListItemBinding coronaListItemBinding =
                DataBindingUtil.inflate(LayoutInflater.from(viewGroup.getContext()),
                        R.layout.corona_list_item, viewGroup, false);
        return new CoronaViewHolder(coronaListItemBinding);
    }

    @Override
    public void onBindViewHolder(@NonNull CoronaViewHolder coronaViewHolder, int i) {
        Corona c = corona.get(i);
        coronaViewHolder.coronaListItemBinding.setCorona(c);
    }

    @Override
    public int getItemCount() {
        if (corona != null) {
            return corona.size();
        } else {
            return 0;
        }
    }

    public void setCoronaList(ArrayList<Corona> corona) {
        this.corona = corona;
        notifyDataSetChanged();
    }

    static class CoronaViewHolder extends RecyclerView.ViewHolder {
        private CoronaListItemBinding coronaListItemBinding;

        CoronaViewHolder(@NonNull CoronaListItemBinding coronaListItemBinding) {
            super(coronaListItemBinding.getRoot());
            this.coronaListItemBinding = coronaListItemBinding;
        }
    }
}

6. Retrofit

Bagian ini adalah bagian untuk memanggil API, kita gunakan retrofit untuk memudahkan segala urusan.

6.1 Interface

public interface CoronaDataService {
    @GET("indonesia/provinsi")
    Call<List<Corona>> getCorona();
}

6.2 Buat Retrofit Client

public class RetrofitClient {
    private static Retrofit retrofit;
    private static final String BASE_URL = "https://api.kawalcorona.com/";
    public static CoronaDataService getService() {
        if (retrofit == null) {
            retrofit = new Retrofit
                    .Builder()
                    .baseUrl(BASE_URL)
                    .addConverterFactory(GsonConverterFactory.create())
                    .build();
        }
        return retrofit.create(CoronaDataService.class);
    }
}

7. Buat Repositori

Mari kita membuat Repositori data corona, yang berhubungan dengan network dan menyajikanMutableLiveData instance.

public class CoronaRepository {
    private String TAG = "CoronaRepository";
    private MutableLiveData<List<Corona>> mutableLiveData = new MutableLiveData<>();

    public CoronaRepository() {
    }

    public MutableLiveData<List<Corona>> getMutableLiveData() {
        final CoronaDataService coronaDataService = RetrofitClient.getService();
        Call<List<Corona>> call = coronaDataService.getCorona();
        call.enqueue(new Callback<List<Corona>>() {

            @Override
            public void onResponse(Call<List<Corona>> call, Response<List<Corona>> response) {
                if (response.isSuccessful()) {
                    mutableLiveData.setValue(response.body());;
                }
            }

            @Override
            public void onFailure(Call<List<Corona>> call, Throwable t) {
                Log.e(TAG, "onFailure: " + t.getCause());
                Log.e(TAG, "onFailure: " + t.getLocalizedMessage());
                Log.e(TAG, "onFailure: " + t.getMessage());
                Log.e(TAG, "onFailure: " + t.toString());
            }
        });
        return mutableLiveData;
    }
}

8. Buat MainViewModel

public class MainViewModel  extends AndroidViewModel {

    private CoronaRepository coronaRepository;
    public MainViewModel(@NonNull Application application) {
        super(application);
        coronaRepository = new CoronaRepository();
    }
    public LiveData<List<Corona>> getAllCorona() {
        return coronaRepository.getMutableLiveData();
    }
}

9. Ubah MainActivity

ini adalah langkah terakhir. Deklarasikan semua objek di dalam MainActivity. Pada dasarnya secara ringkas ada 4 langkah dalam pengubahan MainActivity ini :

  1. Set ContentView menggunakan data binding
  2. Bind RecyclerView
  3. Buat instansiasi adapter dan tempatkan di RecyclerView
  4. Ambil data dari server menggunakan CoronaRepository and beritahukan ke adapter
public class MainActivity extends AppCompatActivity {
    private MainViewModel mainViewModel;
    SwipeRefreshLayout swipeRefresh;
    RecyclerView mRecyclerView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ActivityMainBinding activityMainBinding =
                DataBindingUtil.setContentView(this, R.layout.activity_main);

        mainViewModel = ViewModelProviders.of(this).get(MainViewModel.class);
        initializationViews();
        getAllCorona();

        swipeRefresh.setOnRefreshListener(this::getAllCorona);
    }

    private void initializationViews() {
        swipeRefresh = findViewById(R.id.swipeRefresh);
        mRecyclerView = findViewById(R.id.viewCorona);
    }

    private void getAllCorona() {
        swipeRefresh.setRefreshing(true);
        mainViewModel.getAllCorona().observe(this, new Observer<List<Corona>>() {
            @Override
            public void onChanged(@Nullable List<Corona> coronaList) {
                swipeRefresh.setRefreshing(false);
                prepareRecyclerView(coronaList);
            }
        });
    }

    private void prepareRecyclerView(List<Corona> coronaList) {
        CoronaDataAdapter coronaDataAdapter = new CoronaDataAdapter(coronaList);
        mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
        mRecyclerView.setItemAnimator(new DefaultItemAnimator());
        mRecyclerView.setAdapter(coronaDataAdapter);
        coronaDataAdapter.setCoronaList((ArrayList<Corona>) coronaList);
    }
}

Semoga tutorial ini dapat membantu bagi yang masih belajar MVVM ataupun baru berkenalan dengan programming andriod.Tutorial ini terinspirasi dari : https://androidwave.com/android-data-binding-recyclerview/. Tentunya jika ada waktu saya akan perbaharui aplikasi ini misalnya dengan menambahkan filter dan grafik.

Alamat source code : https://github.com/dekikurnia/info-covid19

Post Author: dekikurnia

Suka menulis, main gitar dan baca buku