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.
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 :
- Set
ContentView
menggunakan data binding - Bind
RecyclerView
- Buat instansiasi adapter dan tempatkan di
RecyclerView
- 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