Showing posts with label android studio. Show all posts
Showing posts with label android studio. Show all posts
Monday, June 15, 2020

Belajar Aplikasi Android : Pengujian ViewModel dalam Proyek Academy


Tujuan

Pada Codelab kali ini Anda akan mempelajari bagaimana menguji ViewModel dalam proyek Academy dengan Unit Test dan Instrumental Test.

Logika Dasar

Unit Testing: Melakukan pengujian tiap ViewModel
Instrumental Testing: Melakukan pengujian tiap halaman dan keseluruhan Aplikasi.

Codelab Unit Testing ViewModel

  1. Bukalah proyek Academy yang sudah Anda buat pada modul sebelumnya atau Anda bisa unduh di sini:
  2. Pertama yang perlu dilakukan adalah menuliskan skenario testing yang dilakukan:
    • AcademyViewModelTest:
      • Memuat Courses:
        • Memastikan data course tidak null.
        • Memastikan jumlah data course sesuai dengan yang diharapkan.
    • BookmarkViewModelTest:
      • Memuat Bookmarks:
        • Memastikan data course tidak null.
        • Memastikan jumlah data course sesuai dengan yang diharapkan.
    • DetailCourseViewModelTest:
      • Memuat Course:
        • Memastikan data course tidak null.
        • Memastikan data course sesuai dengan yang diharapkan.
      • Memuat Modules:
        • Memastikan data module tidak null.
        • Memastikan jumlah data module sesuai dengan yang diharapkan.
    • CourseReaderViewModelTest:
      • Memuat Modules:
        • Memastikan data module tidak null.
        • Memastikan jumlah data module sesuai dengan yang diharapkan.
      • Memuat Module yang dipilih:
        • Memastikan data module tidak null.
        • Memastikan data content tidak null.
        • Memastikan value dari content tidak null.
        • Memastikan data content sesuai dengan yang diharapkan.
  3. Bukalah AcademyViewModel, kemudian klik ALT + Enter di bagian AcademyViewModel seperti ini:
    Kotlin
    20191218144310e2ed20653c08886f5e2e9f821ca98084.png
    Java
    RUSSaAYr-Z-C-QQYhrFH0wTVTRlqRa1r2jeN4FOf4XGwR3BVBtAaGtP651ppPf-TyP1W6WMF3os8si6gKVkCp0zU5Tc6DGgAaovO1O8KyISE5q20YpqIOJa9o0a7hjA5DCmtQPmo
    Kemudian pilih Create Test, maka akan muncul tampilan seperti ini:
    201912181444027710e0c66fcaa95193459e07c0746ee3.png
    Biarkan nama default seperti tampilan di atas, kemudian tekan tombol OK maka akan muncul pilihan tujuan pembuatan kelas. Karena Anda akan melakukan Unit Testing, maka pilih folder test.
    r6oMKyQAvaXpJ2-ADRNd6u4lixXneiJ6DgsCRw1j7eSdSZAPp9H33d0w8O98ljqp2Cwd7piXALNTwbl69KO8px3hJc6i99aBX65L9hc-WQcQugMVBffgWKkZEjlg4Cu_egeFCdJ_
    Tekan tombol OK maka secara otomatis akan ada sebuah kelas baru dengan nama AcademyViewModelTesting.
    PAor91YkvsbVRhVI4ENOYtJojYkHB0ljyzihpT1w9hnDvwu1lK68hSPCknia5UvNDJml5DlLkxqTxcNhMQeQuXrjTeuHA9JPWvpJzLdJoWvz-rROsID7728R0LeHNVSgDOeSDM44
    Catatan:Hapuslah ExampleUnitTest, karena kita tidak akan memakai kelas tersebut.
  4. Selanjutnya, bukalah kelas AcademyViewModelTest. Ujilah metode getCourses() dengan memasukkan kode berikut:
    Kotlin
    class AcademyViewModelTest {

    private lateinit var viewModel: AcademyViewModel

    @Before
    fun setUp() {
    viewModel = AcademyViewModel()
    }


    @Test
    fun getCourses() {
    val courseEntities = viewModel.getCourses()
    assertNotNull(courseEntities)
    assertEquals(5, courseEntities.size)

    }
    }
    Java
    public class AcademyViewModelTest {
    private AcademyViewModel viewModel;

    @Before
    public void setUp() {
    viewModel = new AcademyViewModel();
    }


    @Test
    public void getCourses() {
    List<CourseEntity> courseEntities = viewModel.getCourses();
    assertNotNull(courseEntities);
    assertEquals(5, courseEntities.size());

    }
    }
  1. Jalankan pengujian pada kelas AcademyViewModelTest, dengan cara menekan tombol di sebelah kiri kode editor.
    BOaUoXng6TmSnfjxTq8Eif51eYqs1VAJJRrRtTuHjWhxr6j8wbbUU2lGQIfaXvyQGWf2Z7wXrk_3uQxKbj6QrghpMG7SweYRT90GmziKLTvEixTk4OCFdUASdNKzMwTlC-OC0JIL
    Pilihlah Run ‘AcademyViewModelTest’, maka hasil pengujiannya jadi seperti ini:
    YmY0Wb59Y1Qq4pv7tGWpMODcP6n0svzGoIHClq2sQiCrK4hjW6jozFigCTb5aN3RFF9tsnX3eGU9bj3cUDsMLDY4flz72bjnhIbPQJj_Q48-7TD84NTfsZLMtAxv7l5MZO4gTjkb
    Selamat, Anda berhasil menguji AcademyViewModel dengan cara membandingkan ukuran dari array viewModel.getCourses().
  2. Lakukanlah kembali langkah nomor 2 untuk melakukan Unit Testing kelas BookmarkViewModel, DetailCoursesViewModel dan CourseReaderViewModel. Jika Anda sudah melakukan langkah tersebut, maka tampilan kelas pada package test akan jadi seperti ini:
    Cdfzbz0JuUmneBD7Q1mUW775EPwNBtgucdfQToi9rUT-1pVxtyV8FT48Il0Ukoh7Mmh0-1ulJ-ADoGTKfJGztX9HGPY1OgIjQ7rjfF0U9WMxYn8pDJ0nNhJ5IYZS-kwh03WxB0Gy
  3. Bukalah BookmarkViewModelTest, kita akan menguji apakah metode getBookmark sudah benar atau belum. Tambahkan kode berikut:
    Kotlin
    class BookmarkViewModelTest {
    private lateinit var viewModel: BookmarkViewModel

    @Before
    fun setUp() {
    viewModel = BookmarkViewModel()
    }

    @Test
    fun getBookmark() {
    val courseEntities = viewModel.getBookmarks()
    assertNotNull(courseEntities)
    assertEquals(5, courseEntities.size)
    }
    }
    Java
    public class BookmarkViewModelTest {
    private BookmarkViewModel viewModel;

    @Before
    public void setUp() {
    viewModel = new BookmarkViewModel();
    }

    @Test
    public void getBookmark() {
    List<CourseEntity> courseEntities = viewModel.getBookmarks();
    assertNotNull(courseEntities);
    assertEquals(5, courseEntities.size());
    }
    }
    Jika dilihat dari kode di atas, cara pengujian yang dilakukan sama karena data bookmark sama-sama berasal dari kelas DataDummy. Jalankan pengujian tersebut, maka akan jadi seperti ini:
    Qy78LBYP6WNW2lnmxFmOVl7oKkqQtcdHIOcvi116R8Xzc1YxWQU0gkLyo8Ni1xqsxZ7OqYGBlpgrijlLzTrNTKiath5cyLN-zYUic4Pu92HgTk9d-l8v6nj6rO0JSlG3RAxpbiXk
    Sekali lagi, selamat! Anda berhasil melakukan pengujian BookmarkViewModel dengan unit testing. Tapi masih ada 2 kelas ViewModel lagi yang perlu diuji.
  4. Bukalah kelas DetailCourseViewModelTest. Pada kelas ini, terdapat 2 metode sumber data yakni getCourse() dan getModules(). Tambahkan kode berikut untuk melakukan pengujian terhadap 2 metode tersebut:
    Kotlin
    class DetailCourseViewModelTest {
    private lateinit var viewModel: DetailCourseViewModel
    private val dummyCourse = DataDummy.generateDummyCourses()[0]
    private val courseId = dummyCourse.courseId

    @Before
    fun setUp() {
    viewModel = DetailCourseViewModel()
    viewModel.setSelectedCourse(courseId)
    }

    @Test
    fun getCourse() {
    viewModel.setSelectedCourse(dummyCourse.courseId)
    val courseEntity = viewModel.getCourse()
    assertNotNull(courseEntity)
    assertEquals(dummyCourse.courseId, courseEntity.courseId)
    assertEquals(dummyCourse.deadline, courseEntity.deadline)
    assertEquals(dummyCourse.description, courseEntity.description)
    assertEquals(dummyCourse.imagePath, courseEntity.imagePath)
    assertEquals(dummyCourse.title, courseEntity.title)
    }

    @Test
    fun getModules() {
    val moduleEntities = viewModel.getModules()
    assertNotNull(moduleEntities)
    assertEquals(7, moduleEntities.size.toLong())
    }
    }
    Java
    public class DetailCourseViewModelTest {
    private DetailCourseViewModel viewModel;
    private CourseEntity dummyCourse = DataDummy.generateDummyCourses().get(0);
    private String courseId = dummyCourse.getCourseId();

    @Before
    public void setUp() {
    viewModel = new DetailCourseViewModel();
    viewModel.setSelectedCourse(courseId);
    }

    @Test
    public void getCourse() {
    viewModel.setSelectedCourse(dummyCourse.getCourseId());
    CourseEntity courseEntity = viewModel.getCourse();
    assertNotNull(courseEntity);
    assertEquals(dummyCourse.getCourseId(), courseEntity.getCourseId());
    assertEquals(dummyCourse.getDeadline(), courseEntity.getDeadline());
    assertEquals(dummyCourse.getDescription(), courseEntity.getDescription());
    assertEquals(dummyCourse.getImagePath(), courseEntity.getImagePath());
    assertEquals(dummyCourse.getTitle(), courseEntity.getTitle());
    }

    @Test
    public void getModules() {
    List<ModuleEntity> moduleEntities = viewModel.getModules();
    assertNotNull(moduleEntities);
    assertEquals(7, moduleEntities.size());
    }
    }
    Jalankan pengujian pada kelas tersebut, maka hasil pengujiannya jadi seperti ini:
    OGj5E_jgkh6WYf7xt0Mfko-Ia0PShBPh8G7jo3BeaLDkCHL2BZ3H6ov4OvM1248VYtiqN6jmYaS1trXX6moOFumYX5Lq16qgkf0J3y8KBJXx8dEZp-euFusEOCUOK2x-OjbP48Vi
    Selamat, Anda juga berhasil menguji DetailCourseViewModel dengan cara membandingkan ukuran dari array viewModel.getModules() dan membandingkan data CourseEntity hasil dari viewModel.getCourses().
  1. Terakhir, bukalah CourseReaderViewModelTest. Pada kelas CourseReaderViewModel, terdapat 2 metode yang harus diuji yakni getSelectedModule dan getModules. Tambahkan kode berikut untuk melakukan pengujian:
    Kotlin
    class CourseReaderViewModelTest {

    private lateinit var viewModel: CourseReaderViewModel

    private val dummyCourse = DataDummy.generateDummyCourses()[0]
    private val courseId = dummyCourse.courseId
    private val dummyModules = DataDummy.generateDummyModules(courseId)
    private val moduleId = dummyModules[0].moduleId

    @Before
    fun setUp() {
    viewModel = CourseReaderViewModel()
    viewModel.setSelectedCourse(courseId)
    viewModel.setSelectedModule(moduleId)

    val dummyModule = dummyModules[0]
    dummyModule.contentEntity = ContentEntity("<h3 class=\\\"fr-text-bordered\\\">"+dummyModule.title+"</h3><p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>")
    }

    @Test
    fun getModules() {
    val moduleEntities = viewModel.getModules()
    assertNotNull(moduleEntities)
    assertEquals(7, moduleEntities.size.toLong())
    }

    @Test
    fun getSelectedModule() {
    val moduleEntity = viewModel.getSelectedModule()
    assertNotNull(moduleEntity)
    val contentEntity = moduleEntity.contentEntity
    assertNotNull(contentEntity)
    val content = contentEntity?.content
    assertNotNull(content)
    assertEquals(content, dummyModules[0].contentEntity?.content)
    }
    }
    Java
    public class CourseReaderViewModelTest {

    private CourseReaderViewModel viewModel;

    private CourseEntity dummyCourse = DataDummy.generateDummyCourses().get(0);
    private String courseId = dummyCourse.getCourseId();
    private ArrayList<ModuleEntity> dummyModules = DataDummy.generateDummyModules(courseId);
    private String moduleId = dummyModules.get(0).getModuleId();

    @Before
    public void setUp() {
    viewModel = new CourseReaderViewModel();
    viewModel.setSelectedCourse(courseId);
    viewModel.setSelectedModule(moduleId);

    ModuleEntity dummyModule = dummyModules.get(0);
    dummyModule.contentEntity = new ContentEntity("<h3 class=\\\"fr-text-bordered\\\">" + dummyModule.getTitle() + "</h3><p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>");
    }

    @Test
    public void getModules() {
    ArrayList<ModuleEntity> moduleEntities = viewModel.getModules();
    assertNotNull(moduleEntities);
    assertEquals(7, moduleEntities.size());
    }

    @Test
    public void getSelectedModule() {
    ModuleEntity moduleEntity = viewModel.getSelectedModule();
    assertNotNull(moduleEntity);
    ContentEntity contentEntity = moduleEntity.contentEntity;
    assertNotNull(contentEntity);
    String content = contentEntity.getContent();
    assertNotNull(content);
    assertEquals(content, dummyModules.get(0).contentEntity.getContent());
    }
    }
    Jalankan pengujian pada kelas tersebut, maka hasil pengujiannya jadi seperti ini:
    W2eCsJkDgtQmelUwGo4Z5ozmNpFZhgt4a3Pw6hXWLCaBRiG38yoMnL-YDJTuMHOMhXqyWBXrpMjYStszUqYHofBbJmvRaiynTDJqgUbZg68f5lLKg8DCbpE_s7jg3RXANxDtzaX0
    Selamat, Anda juga berhasil menguji CourseReaderViewModel dengan cara membandingkan ukuran dari array viewModel.getModules() dan membandingkan data ModuleEntity hasil dari viewModel.getSelectedModule().
Mantap! Anda sudah melakukan pengujian setiap ViewModel yang ada pada proyek Academy. Namun, Anda juga perlu melakukan Instrumental Test untuk menguji apakah secara ui, kode Anda sudah berjalan sudah benar.

Codelab Instrumental Testing ViewModel

  1. Sebelum Anda melakukan Instrumental Testing, Anda perlu membuat skenario testing yang dilakukan:
    • Menampilkan data kursus academy
      • Memastikan rv_academy dalam keadaan tampil.
      • Gulir rv_academy ke posisi data terakhir.
    • Menampilkan data detail kursus academy
      • Memberi tindakan klik pada data pertama di rv_academy.
      • Memastikan TextView untuk title tampil sesuai dengan yang diharapkan.
      • Memastikan TextView untuk deadline tampil sesuai dengan yang diharapkan.
    • Menampilkan data modul
      • Memberi tindakan klik pada data pertama di rv_academy.
      • Memberi tindakan klik pada btn_start.
      • Memastikan rv_module dalam keadaan tampil.
    • Menampilkan data kursus academy
      • Memberi tindakan klik pada data pertama di rv_academy.
      • Memberi tindakan klik pada btn_start.
      • Memberi tindakan klik pada data pertama di rv_module
      • Memastikan web_view sudah tampil.
    • Menampilkan data bookmark
      • Klik TabLayout dengan teks Bookmark
      • Memastikan rv_module dalam keadaan tampil.
      • Gulir rv_module ke posisi data terakhir.
  2. Selanjutnya Anda perlu menambahkan library yang digunakan untuk pengujian. Bukalah build.gradle (project), tambahkan kode berikut:
    ext {
    //dependencies version
    appCompatVersion = '1.1.0'
    coreVersion = '1.1.0'
    constraintLayoutVersion = '1.1.3'
    junitVersion = '4.12'
    espressoVersion = '3.1.0'
    androidXTestVersion = '1.2.0'

    materialVersion = '1.0.0'
    recyclerViewVersion = '1.1.0'
    glideVersion = '4.10.0'

    archLifecycleVersion = '2.1.0'
    }
    Kemudian bukalah build.gradle (module:app), tambahkan kode berikut:
    androidTestImplementation "androidx.test.espresso:espresso-contrib:$espressoVersion"
    androidTestImplementation "androidx.test:rules:$androidXTestVersion"

  3. Bukalah HomeActivity, kemudian klik ALT + Enter di bagian HomeActivity seperti ini:
    Kotlin
    20191219162518840eba9134f6227ef3911c261e8335d3.png
    Java
    20191219162429d3ff1bf0d8036a0d373441875f6d2f9d.png
    Kemudian pilih Create Test, maka akan muncul tampilan seperti ini:
    XkFdFVGKw1coL_G2AyeTsrDKvWEj7t7mEA-Z1kxkmuxicu8MKYIWJojaro0pzDn_UNwSdp_RPu8P4QmFbRWRzeZU83jJW9zIJcrWqOsv8IfGxzhawdH00EYLQu-2bRNK18VF88gr
    Biarkan nama default seperti tampilan di atas, kemudian tekan tombol OK maka akan muncul pilihan tujuan pembuatan kelas. Karena Anda akan melakukan Instrumental Testing, maka pilih folder androidTest.
    M-Xv8FztrMbBEZskoGTJHw0qdUKjYqA3luMxlyEXY8jyVOeDO11WIc6nrvmNW1j3MGRG_Ob2ww-3WSbEI8H_W5qrIxtxsaINPc3RfPNWSBJzGLL3aX85dTYK1SefBUuSkQi0eze6
    Tekan tombol OK, secara otomatis akan ada sebuah kelas baru dengan nama HomeActivityTest.
    201912191626364cf4e9b622b6703e8a50442f1fa4c063.pngCatatan:Hapuslah ExampleInstrumentedTest, karena kita tidak akan memakai kelas tersebut.
  4. Bukalah kelas HomeAcademyTest, buatlah pengujian sesuai dengan skenario seperti berikut:
    Kotlin
    class HomeActivityTest {

    private val dummyCourse = DataDummy.generateDummyCourses()

    @get:Rule
    var activityRule = ActivityTestRule(HomeActivity::class.java)

    @Test
    fun loadCourses() {
    onView(withId(R.id.rv_academy)).check(matches(isDisplayed()))
    onView(withId(R.id.rv_academy)).perform(RecyclerViewActions.scrollToPosition<RecyclerView.ViewHolder>(dummyCourse.size))
    }

    @Test
    fun loadDetailCourse() {
    onView(withId(R.id.rv_academy)).perform(RecyclerViewActions.actionOnItemAtPosition<RecyclerView.ViewHolder>(0, click()))
    onView(withId(R.id.text_title)).check(matches(isDisplayed()))
    onView(withId(R.id.text_title)).check(matches(withText(dummyCourse[0].title)))
    onView(withId(R.id.text_date)).check(matches(isDisplayed()))
    onView(withId(R.id.text_date)).check(matches(withText("Deadline ${dummyCourse[0].deadline}")))
    }

    @Test
    fun loadModule() {
    onView(withId(R.id.rv_academy)).perform(RecyclerViewActions.actionOnItemAtPosition<RecyclerView.ViewHolder>(0, click()))
    onView(withId(R.id.btn_start)).perform(click())
    onView(withId(R.id.rv_module)).check(matches(isDisplayed()))
    }

    @Test
    fun loadDetailModule() {
    onView(withId(R.id.rv_academy)).perform(RecyclerViewActions.actionOnItemAtPosition<RecyclerView.ViewHolder>(0, click()))
    onView(withId(R.id.btn_start)).perform(click())
    onView(withId(R.id.rv_module)).perform(RecyclerViewActions.actionOnItemAtPosition<RecyclerView.ViewHolder>(0, click()))
    onView(withId(R.id.web_view)).check(matches(isDisplayed()))
    }

    @Test
    fun loadBookmarks() {
    onView(withText("Bookmark")).perform(click())
    onView(withId(R.id.rv_bookmark)).check(matches(isDisplayed()))
    onView(withId(R.id.rv_bookmark)).perform(RecyclerViewActions.scrollToPosition<RecyclerView.ViewHolder>(dummyCourse.size))
    }
    }
    Java
    public class HomeActivityTest {

    private ArrayList<CourseEntity> dummyCourse = DataDummy.generateDummyCourses();

    @Rule
    public ActivityTestRule activityRule = new ActivityTestRule<>(HomeActivity.class);

    @Test
    public void loadCourses() {
    onView(withId(R.id.rv_academy)).check(matches(isDisplayed()));
    onView(withId(R.id.rv_academy)).perform(RecyclerViewActions.scrollToPosition(dummyCourse.size()));
    }

    @Test
    public void loadDetailCourse() {
    onView(withId(R.id.rv_academy)).perform(RecyclerViewActions.actionOnItemAtPosition(0, click()));
    onView(withId(R.id.text_title)).check(matches(isDisplayed()));
    onView(withId(R.id.text_title)).check(matches(withText(dummyCourse.get(0).getTitle())));
    onView(withId(R.id.text_date)).check(matches(isDisplayed()));
    onView(withId(R.id.text_date)).check(matches(withText(String.format("Deadline %s", dummyCourse.get(0).getDeadline()))));
    }

    @Test
    public void loadModule() {
    onView(withId(R.id.rv_academy)).perform(RecyclerViewActions.actionOnItemAtPosition(0, click()));
    onView(withId(R.id.btn_start)).perform(click());
    onView(withId(R.id.rv_module)).check(matches(isDisplayed()));
    }

    @Test
    public void loadDetailModule() {
    onView(withId(R.id.rv_academy)).perform(RecyclerViewActions.actionOnItemAtPosition(0, click()));
    onView(withId(R.id.btn_start)).perform(click());
    onView(withId(R.id.rv_module)).perform(RecyclerViewActions.actionOnItemAtPosition(0, click()));
    onView(withId(R.id.web_view)).check(matches(isDisplayed()));
    }

    @Test
    public void loadBookmarks() {
    onView(withText("Bookmark")).perform(click());
    onView(withId(R.id.rv_bookmark)).check(matches(isDisplayed()));
    onView(withId(R.id.rv_bookmark)).perform(RecyclerViewActions.scrollToPosition(dummyCourse.size()));
    }
    }
  5. Kemudian jalankan test dengan cara klik kanan pada class HomeActivityTest dan klik Run 'HomeActivityTest'.
    201912191631492bc45f09c5718f762d18924a415e6c7f.pngBisa juga dengan klik icon hijau seperti gambar ini:
    20191219163301cef43de77e20dbc99eccdefeace2c757.pngMaka device akan otomatis melakukan aksi sesuai dengan yang diskenariokan seperti ini:
    20191219163755ae46abdd98fe2054fc4fa0dce9a7bda9.gifDan kalau dilihat hasil testnya seperti ini:20191219163640a7308d0a2ccd712128964976570b1950.pngSelamat! Anda berhasil menguji AcademyTest.

Bedah Kode

UnitTest

Materi unit test secara umum sama dengan yang ada di modul latihan. Anda bisa lihat kembali untuk fungsi test yang digunakan seperti assert, verify, when() dll.

Tiga Komponen Utama dari Espresso:

Anda menggunakan Espresso dalam melakukan Instrumental Testing, berikut adalah 3 komponen utamanya:
  1. ViewMatchers (onView(ViewMatcher)): untuk menemukan elemen atau komponen antarmuka yang diuji.
  2. ViewActions (perform(ViewAction)): untuk memberikan event untuk melakukan sebuah aksi pada komponen antarmuka yang diuji.
  3. ViewAssertions: sebuah kondisi atau state dari komponen yang diuji.
Perhatikan kode berikut:
Kotlin
@get:Rule
var activityRule = ActivityTestRule(HomeActivity::class.java)
Java
@Rule
public ActivityTestRule activityRule = new ActivityTestRule<>(HomeActivity.class);
Annotation Rule digunakan untuk memanggil activity mana yang akan di-launch atau diluncurkan.

Selain itu perhartikan kode berikut:
Kotlin
onView(withId(R.id.rv_academy)).perform(RecyclerViewActions.scrollToPosition<RecyclerView.ViewHolder>(dummyCourse.size))
...
onView(withId(R.id.rv_academy)).perform(RecyclerViewActions.actionOnItemAtPosition<RecyclerView.ViewHolder>(0, click()))
Java
onView(withId(R.id.rv_academy)).perform(RecyclerViewActions.scrollToPosition(dummyCourse.size()));
...
onView(withId(R.id.rv_academy)).perform(RecyclerViewActions.actionOnItemAtPosition(0, click()));
Jika diperhartikan, di sini terdapat kode RecyclerViewActions yang digunakan untuk pengujian pada RecyclerView seperti scroll ke posisi tertentu dan klik pada posisi tertentu. Kode ini merupakan hasil dari penambahan library
androidTestImplementation "androidx.test.espresso:espresso-contrib:$espressoVersion"

Menarik bukan? Jadi ketika Anda membuat sebuah fitur, Anda perlu menyiapkan skenario pengujian untuk menguji fitur tersebut. Karena jika tidak, Aplikasi yang Anda buat bisa saja menyimpan eror atau bug yang tidak Anda ketahui.
Anda bisa unduh proyek Academy tentang Pengujian ViewModel di sini:
Codelabs selanjutnya akan membahas tentang Repository yang ada pada proyek Academy. Tetap semangat!

Belajar Aplikasi Android Studi Kasus Proyek Akademi : ViewModel dalam Proyek Academy


Tujuan

Pada Codelab kali ini Anda akan mempelajari bagaimana mengimplementasikan ViewModel dalam proyek Academy. Hasil dari codelab kali ini akan menjadi seperti ini:


20191218113703161173adba65c0c43802822c65f17d2f.gif

Logika Dasar

Membuka Aplikasi → memanggil DataDummy ke ViewModel → mengirim data ke Activity → melakukan perubahan rotasi → data masih terjaga.

Codelab ViewModel

  1. Bukalah proyek Academy yang sudah Anda buat sebelumnya atau Anda bisa unduh di sini.
  2. Lihatlah terlebih dahulu susunan package dan kelas yang sudah ada:
    20191218093730623be083ab749083b7d0476f6387c572.pngAnda akan membuat beberapa kelas ViewModel yang nantinya akan digunakan tiap Fragment atau Activity.
  3. Pertama, buka build.gradle level project dan tambahkan versi untuk library berikut:
    ext {
    ...
    archLifecycleVersion = '2.1.0'
    }
    Setelah itu, buka build.gradle level module: app dan tambahkan library berikut:
    dependencies {
    ...
    //architecture component
    implementation "androidx.lifecycle:lifecycle-viewmodel:$archLifecycleVersion"
    }
  4. Buatlah sebuah kelas baru di package academy dengan nama AcademyViewModel.
    7TmW-FNpmImK1rgSAvsCcbh42d6Jeh2AnLAcSZqtQn6LN0gJmWmJ9SN3JKWOLxGpfNd6_SGXRhR6xrtDIzlTMAcN5KKJrkuOPLmKNawTSYKCoiOE2vlyrars9l91aXA5Zjy4tbOq
    Kemudian pindahkan pemanggilan generateDummyCourse() dari AcademyFragment ke kelas AcademyViewModel:
    Kotlin
    class AcademyViewModel : ViewModel() {

    fun getCourses(): List<CourseEntity> = DataDummy.generateDummyCourses()
    }
    Java
    public class AcademyViewModel extends ViewModel {

    public List<CourseEntity> getCourses() {
    return DataDummy.generateDummyCourses();
    }

    }
  1. Kemudian hubungkan AcademyViewModel dengan AcademyFragment. Bukalah AcademyFragment dan tambahkan kode berikut:
    Kotlin
    class AcademyFragment : Fragment() {
    ...

    override fun onActivityCreated(savedInstanceState: Bundle?) {
    super.onActivityCreated(savedInstanceState)
    if (activity != null) {
    val viewModel = ViewModelProvider(this, ViewModelProvider.NewInstanceFactory())[AcademyViewModel::class.java]
    val courses = viewModel.getCourses()

    val academyAdapter = AcademyAdapter()
    academyAdapter.setCourses(courses)

    rv_academy.layoutManager = LinearLayoutManager(context)
    rv_academy.setHasFixedSize(true)
    rv_academy.adapter = academyAdapter
    }
    }
    }
    Java
    public class AcademyFragment extends Fragment {
    ...

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    if (getActivity() != null) {
    AcademyViewModel viewModel = new ViewModelProvider(this, new ViewModelProvider.NewInstanceFactory()).get(AcademyViewModel.class);
    List<CourseEntity> courses = viewModel.getCourses();


    AcademyAdapter academyAdapter = new AcademyAdapter();
    academyAdapter.setCourses(courses);

    rvCourse.setLayoutManager(new LinearLayoutManager(getContext()));
    rvCourse.setHasFixedSize(true);
    rvCourse.setAdapter(academyAdapter);
    }
    }
    }
    Dengan begitu, sumber data sudah dipindahkan ke kelas ViewModel. Jalankan Aplikasi Academy, maka hasilnya akan seperti ini:
    201912180953227d294c14169842b541dbc1d651903a66.pngSecara tampilan tidak ada perubahan, karena Anda hanya memindahkan pengambilan data yang sebelumnya dari Activity menjadi ViewModel.
  1. Selanjutnya buatlah kelas ViewModel di package bookmark dan beri nama BookmarkViewModel.
    N4kBwz38aKAcvoAjNR78miJTBWMzhcjqWZLv-yoW8JnYqjTtvalMugC3VeLXoOuAQD5m_Ip4ZSPfjSlXPrA0Th8rIir42kZaUtpVeWRrLO-gBHocBGsAEorTH6OA6QUKbzkOX8TW
    Pindahkan pemanggilan generateDummyCourse() dari BookmarkFragment ke kelas BookmarkViewModel:
    Kotlin
    class BookmarkViewModel : ViewModel() {

    fun getBookmarks(): List<CourseEntity> = DataDummy.generateDummyCourses()
    }
    Java
    public class BookmarkViewModel extends ViewModel {
    List<CourseEntity> getBookmarks() {
    return DataDummy.generateDummyCourses();
    }

    }
  1. Kemudian hubungkan BookmarkAcademy dengan BookmarkFragment. Bukalah BookmarkAcademy dan tambahkan kode berikut:
    Kotlin
    class BookmarkFragment : Fragment(), BookmarkFragmentCallback {
    ...

    override fun onActivityCreated(savedInstanceState: Bundle?) {
    super.onActivityCreated(savedInstanceState)
    if (activity != null) {
    val viewModel = ViewModelProvider(this, ViewModelProvider.NewInstanceFactory())[BookmarkViewModel::class.java]
    val courses = viewModel.getBookmarks()


    val adapter = BookmarkAdapter(this)
    adapter.setCourses(courses)

    rv_bookmark.layoutManager = LinearLayoutManager(context)
    rv_bookmark.setHasFixedSize(true)
    rv_bookmark.adapter = adapter
    }
    }

    ...
    }
    Java
    public class BookmarkFragment extends Fragment implements BookmarkFragmentCallback {
    ...

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    if (getActivity() != null) {
    BookmarkViewModel viewModel = new ViewModelProvider(this, new ViewModelProvider.NewInstanceFactory()).get(BookmarkViewModel.class);
    List<CourseEntity> courses = viewModel.getBookmarks();


    BookmarkAdapter adapter = new BookmarkAdapter(this);
    adapter.setCourses(courses);

    rvBookmark.setLayoutManager(new LinearLayoutManager(getContext()));
    rvBookmark.setHasFixedSize(true);
    rvBookmark.setAdapter(adapter);
    }
    }

    ...
    }
    Dengan begitu, sumber data sudah dipindahkan ke kelas ViewModel.
  1. Selanjutnya buatlah kelas ViewModel untuk DetailCourseActivity di package detail dan beri nama DetailCourseViewModel.
    t3ub-LoQc-zsvlVnoHAajLLZKzd5SEMzgCs3QiO6INIQWabdvvLL-tOE3XRO4x1fUu5PUzgme3Hn-mpeERSAy53ut7VFhC7iTC3a9ZLIXXA1U_O764nicWh1KQkqLMIye3fV0l9d
    Tambahkan kode pada kelas tersebut untuk menetapkan atau mendapatkan courseId, mendapatkan list module dan mendapatkan CourseEntity.
    Kotlin
    class DetailCourseViewModel : ViewModel() {
    private lateinit var courseId: String

    fun setSelectedCourse(courseId: String) {
    this.courseId = courseId
    }

    fun getCourse(): CourseEntity {
    lateinit var course: CourseEntity
    val coursesEntities = DataDummy.generateDummyCourses()
    for (courseEntity in coursesEntities) {
    if (courseEntity.courseId == courseId) {
    course = courseEntity
    }
    }
    return course
    }

    fun getModules(): List<ModuleEntity> = DataDummy.generateDummyModules(courseId)
    }
    Java
    public class DetailCourseViewModel extends ViewModel {
    private String courseId;

    public void setSelectedCourse(String courseId) {
    this.courseId = courseId;
    }

    public CourseEntity getCourse() {
    CourseEntity course = null;
    ArrayList<CourseEntity> courseEntities = DataDummy.generateDummyCourses();
    for (CourseEntity courseEntity : courseEntities) {
    if (courseEntity.getCourseId().equals(courseId)) {
    course = courseEntity;
    }
    }
    return course;
    }

    public List<ModuleEntity> getModules() {
    return DataDummy.generateDummyModules(courseId);
    }
    }
  1. Selanjutnya ubahlah kode yang ada di DetailCourseActivity untuk menghubungkan DetailCourseViewModel.
    Kotlin
    class DetailCourseActivity : AppCompatActivity() {
    ...

    override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_detail_course)
    ...

    val viewModel = ViewModelProvider(this, ViewModelProvider.NewInstanceFactory())[DetailCourseViewModel::class.java]

    val extras = intent.extras
    if (extras != null) {
    val courseId = extras.getString(EXTRA_COURSE)
    if (courseId != null) {
    viewModel.setSelectedCourse(courseId)
    val modules = viewModel.getModules()
    adapter.setModules(modules)
    populateCourse(viewModel.getCourse())
    }
    }

    ...
    }

    ...
    }
    Java
    public class DetailCourseActivity extends AppCompatActivity {
    ...

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_detail_course);
    ...

    DetailCourseViewModel viewModel = new ViewModelProvider(this, new ViewModelProvider.NewInstanceFactory()).get(DetailCourseViewModel.class);

    Bundle extras = getIntent().getExtras();
    if (extras != null) {
    String courseId = extras.getString(EXTRA_COURSE);
    if (courseId != null) {
    viewModel.setSelectedCourse(courseId);
    List<ModuleEntity> modules = viewModel.getModules();
    adapter.setModules(modules);
    populateCourse(viewModel.getCourse());

    }
    }

    ...
    }

    ...
    }
    Dengan bantuan kelas ViewModel, courseId akan dipertahankan sampai Activity masuk ke state onDestroy.

  1. Buatlah kembali kelas ViewModel yang akan digunakan untuk CourseReaderActivity. Kemudian beri nama CourseReaderViewModel.
    Nf7nR4S4nZqKu5FkaQ1fedHKVDyRUVXQ41KTHG52Iavdw-mNQuVmB8UXTZUPrT756r6L6irqlasINvD-atWg_2XB-bdiesRLWMhSb7AUr8IYrT8fpX4lAsaTh6Vbs8wzv-vNKC9C
    Setelah itu tambahkanlah kode di kelas tersebut:
    Kotlin
    class CourseReaderViewModel : ViewModel() {

    private lateinit var courseId: String
    private lateinit var moduleId: String

    fun setSelectedCourse(courseId: String) {
    this.courseId = courseId
    }

    fun setSelectedModule(moduleId: String) {
    this.moduleId = moduleId
    }

    fun getModules(): ArrayList<ModuleEntity> = DataDummy.generateDummyModules(courseId)

    fun getSelectedModule(): ModuleEntity {
    lateinit var module: ModuleEntity
    val moduleEntities = getModules()
    for (moduleEntity in moduleEntities) {
    if (moduleEntity.moduleId == moduleId) {
    module = moduleEntity
    module.contentEntity = ContentEntity("<h3 class=\\\"fr-text-bordered\\\">" + module.title + "</h3><p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>")
    break
    }
    }
    return module
    }
    }
    Java
    public class CourseReaderViewModel extends ViewModel {

    private String courseId;
    private String moduleId;

    public void setSelectedCourse(String courseId) {
    this.courseId = courseId;
    }

    public void setSelectedModule(String moduleId) {
    this.moduleId = moduleId;
    }

    public ArrayList<ModuleEntity> getModules() {
    return DataDummy.generateDummyModules(courseId);
    }

    public ModuleEntity getSelectedModule() {
    ModuleEntity module = null;
    ArrayList<ModuleEntity> moduleEntities = getModules();
    for (ModuleEntity moduleEntity: moduleEntities) {
    if (moduleEntity.getModuleId().equals(moduleId)) {
    module = moduleEntity;
    module.contentEntity = new ContentEntity("<h3 class=\\\"fr-text-bordered\\\">" + module.getTitle() + "</h3><p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>");
    break;
    }
    }
    return module;
    }
    }
    CourseReaderViewModel nantinya juga akan digunakan di ModuleContentFragment dan ModuleListFragment.
  1. Setelah membuat kelas ViewModel, bukalah CourseReaderActivity dan sesuaikanlah menjadi seperti ini:
    Kotlin
    class CourseReaderActivity : AppCompatActivity(), CourseReaderCallback {

    ...

    override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_course_reader)
    val viewModel = ViewModelProvider(this, ViewModelProvider.NewInstanceFactory())[CourseReaderViewModel::class.java]

    val bundle = intent.extras
    if (bundle != null) {
    val courseId = bundle.getString(EXTRA_COURSE_ID)
    if (courseId != null) {
    viewModel.setSelectedCourse(courseId)
    populateFragment()
    }
    }
    }
    }
    Java
    public class CourseReaderActivity extends AppCompatActivity implements CourseReaderCallback {
    ...

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_course_reader);

    CourseReaderViewModel viewModel = new ViewModelProvider(this, new ViewModelProvider.NewInstanceFactory()).get(CourseReaderViewModel.class);

    Bundle bundle = getIntent().getExtras();
    if (bundle != null) {
    String courseId = bundle.getString(EXTRA_COURSE_ID);
    if (courseId != null) {
    viewModel.setSelectedCourse(courseId);
    populateFragment();
    }
    }
    }

    ...
    }
  1. Selanjutnya bukalah ModuleListFragment, dan ubah menjadi seperti ini:
    Kotlin
    class ModuleListFragment : Fragment(), MyAdapterClickListener {
    ...

    private lateinit var viewModel: CourseReaderViewModel

    ...

    override fun onActivityCreated(savedInstanceState: Bundle?) {
    super.onActivityCreated(savedInstanceState)
    viewModel = ViewModelProvider(requireActivity(), ViewModelProvider.NewInstanceFactory())[CourseReaderViewModel::class.java]
    adapter = ModuleListAdapter(this)
    populateRecyclerView(viewModel.getModules())

    }

    ...

    override fun onItemClicked(position: Int, moduleId: String) {
    courseReaderCallback.moveTo(position, moduleId)
    viewModel.setSelectedModule(moduleId)
    }

    ...
    }
    Java
    public class ModuleListFragment extends Fragment implements MyAdapterClickListener {

    ...

    private CourseReaderViewModel viewModel;

    ...

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    if (getActivity() != null) {
    viewModel = new ViewModelProvider(requireActivity(), new ViewModelProvider.NewInstanceFactory()).get(CourseReaderViewModel.class);
    adapter = new ModuleListAdapter(this);
    populateRecyclerView(viewModel.getModules());
    }

    }

    ...

    @Override
    public void onItemClicked(int position, String moduleId) {
    courseReaderCallback.moveTo(position, moduleId);
    viewModel.setSelectedModule(moduleId);
    }
    }
    Dengan menambahkan ViewModel di atas, maka sumber data sudah dipindahkan ke ViewModel.
  1. Bukalah ModuleContentFragment dan sesuaikanlah kode pada kelas tersebut menjadi seperti ini:
    Kotlin
    class ModuleContentFragment : Fragment() {

    ...

    override fun onActivityCreated(savedInstanceState: Bundle?) {
    super.onActivityCreated(savedInstanceState)
    if (activity != null) {
    val viewModel = ViewModelProvider(requireActivity(), ViewModelProvider.NewInstanceFactory())[CourseReaderViewModel::class.java]
    val module = viewModel.getSelectedModule()

    populateWebView(module)
    }
    }

    private fun populateWebView(module: ModuleEntity) {
    web_view.loadData(module.contentEntity?.content, "text/html", "UTF-8")
    }
    }
    Java
    public class ModuleContentFragment extends Fragment {
    ...

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    if (getActivity() != null) {
    CourseReaderViewModel viewModel = new ViewModelProvider(requireActivity(), new ViewModelProvider.NewInstanceFactory()).get(CourseReaderViewModel.class);
    ModuleEntity module = viewModel.getSelectedModule();
    populateWebView(module);
    }
    }

    private void populateWebView(ModuleEntity module) {
    webView.loadData(module.contentEntity.getContent(), "text/html", "UTF-8");
    }
    }
    Mengapa ModuleContentFragment bisa langsung tahu ModuleEntity? Jika Anda lihat, tidak ada masukan courseId dan moduleId. Hal ini bisa terjadi karena courseId sudah dimasukkan di CourseReaderActivity dan moduleId dimasukkan di ModuleListFragment. Inilah yang disebut share ViewModel, membagikan ViewModel ke kelas lain. Jadi perlu diperhatikan, pemanggil ViewModel dengan Fragment tersebut:
    Kotlin
    viewModel = ViewModelProvider(requireActivity(), ViewModelProvider.NewInstanceFactory())[CourseReaderViewModel::class.java]
    Java
    viewModel = new ViewModelProvider(requireActivity(), new ViewModelProvider.NewInstanceFactory()).get(CourseReaderViewModel.class);
    Catatan:Jika Anda ganti requireActivity() dengan this, maka Fragment tidak akan mengambil ViewModel dari Activity tetapi akan membuat ViewModel baru.
  1. Langkah terakhir adalah menjalankan aplikasi Anda dan tampilannya akan jadi seperti ini:2019121811345450fe29586c4ebef713ec4a0a075497f6.gifJika dilihat, tidak ada perbedaan tampilan awal karena Anda hanya memindahkan data yang awalnya di Activity menjadi ViewModel. Yang berbeda yaitu tampilan di tiap modul sekarang berbeda-beda sesuai dengan modul yang dipilih, tidak seperti sebelumnya yang sama semua.

Bedah Kode

ViewModel

Perhatikan pemanggilan ViewModel berikut:
AcademyFragment
Kotlin
val viewModel = ViewModelProvider(this, ViewModelProvider.NewInstanceFactory())[AcademyViewModel::class.java]
Java
AcademyViewModel viewModel = new ViewModelProvider(this, new ViewModelProvider.NewInstanceFactory()).get(AcademyViewModel.class);
DetailCourseActivity
Kotlin
val viewModel = ViewModelProvider(this, ViewModelProvider.NewInstanceFactory())[DetailCourseViewModel::class.java]
Java
DetailCourseViewModel viewModel = new ViewModelProvider(this, new ViewModelProvider.NewInstanceFactory()).get(DetailCourseViewModel.class);
Untuk pemanggilan ViewModel antara Activity dengan Fragment itu sama. Yang membedakan ketika Fragment akan menggunakan ViewModel yang ada pada Activity (shared ViewModel). Contohnya adalah seperti ini:
ModelContentFragment
Kotlin
val viewModel = ViewModelProvider(requireActivity(), ViewModelProvider.NewInstanceFactory())[CourseReaderViewModel::class.java]
Java
CourseReaderViewModel viewModel = new ViewModelProvider(requireActivity(), new ViewModelProvider.NewInstanceFactory()).get(CourseReaderViewModel.class);
Jadi this diganti dengan requireActivity() untuk menghubungkan Fragment dengan ViewModel yang dipakai di Activity.

Anda bisa unduh proyek Academy tentang ViewModel di sini:
Codelabs selanjutnya akan membahas tentang unit testing dan instrumental testing yang ada pada proyek Academy. Tetap semangat!

Belajar Aplikasi Android Studi Kasus Proyek Akademi : Menghubungkan Activity & Fragment


Codelab Menghubungkan Activity dan Fragment

Pada codelab ini Anda akan menghubungkan tiap asset-asset dan layout dengan fragment dan Activity. Mari kita mulai dengan mengubah kode yang ada di dalam package ui:

  1. Buatlah sebuah kelas AcademyAdapter untuk menampilkan item untuk RecyclerView di package academy.
    4-j4CfW8zLqnFHb6v_ZlubbGLyps2-rKhilq_phtHafSmyd2iGN6zqAqlbtPiiY4BJB_y4VSY7_mD337jatwQXfitwB-uCZdJpG5Somx_64nR9uSFO4orjWT4ZGvl_V29rs65rUs
    Tambahkanlah kode berikut untuk setCourses, membuat kelas ViewHolder, mem-binding ViewHolder dan mengirim data ke DetailActivity.
    Kotlin
    class AcademyAdapter : RecyclerView.Adapter<AcademyAdapter.CourseViewHolder>() {
    private var listCourses = ArrayList<CourseEntity>()

    fun setCourses(courses: List<CourseEntity>?) {
    if (courses == null) return
    listCourses.clear()
    listCourses.addAll(courses)
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CourseViewHolder {
    val view = LayoutInflater.from(parent.context).inflate(R.layout.items_academy, parent, false)
    return CourseViewHolder(view)
    }

    override fun onBindViewHolder(holder: CourseViewHolder, position: Int) {
    val course = listCourses[position]
    holder.bind(course)
    }

    override fun getItemCount(): Int = listCourses.size


    class CourseViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
    fun bind(course: CourseEntity) {
    with(itemView) {
    tv_item_title.text = course.title
    tv_item_description.text = course.description
    tv_item_date.text = resources.getString(R.string.deadline_date, course.deadline)
    setOnClickListener {
    val intent = Intent(context, DetailCourseActivity::class.java).apply {
    putExtra(DetailCourseActivity.EXTRA_COURSE, course.courseId)
    }
    context.startActivity(intent)
    }
    Glide.with(context)
    .load(course.imagePath)
    .apply(RequestOptions.placeholderOf(R.drawable.ic_loading)
    .error(R.drawable.ic_error))
    .into(img_poster)
    }
    }
    }
    }
    Java
    public class AcademyAdapter extends RecyclerView.Adapter<AcademyAdapter.CourseViewHolder> {
    private List<CourseEntity> listCourses = new ArrayList<>();

    void setCourses(List<CourseEntity> listCourses) {
    if (listCourses == null) return;
    listCourses.clear();
    listCourses.addAll(listCourses);
    }

    @NonNull
    @Override
    public CourseViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
    View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.items_academy, parent, false);
    return new CourseViewHolder(view);
    }

    @Override
    public void onBindViewHolder(@NonNull final CourseViewHolder holder, int position) {
    CourseEntity course = listCourses.get(position);
    holder.bind(course);
    }

    @Override
    public int getItemCount() {
    return listCourses.size();
    }

    class CourseViewHolder extends RecyclerView.ViewHolder {
    final TextView tvTitle;
    final TextView tvDescription;
    final TextView tvDate;
    final ImageView imgPoster;

    CourseViewHolder(View itemView) {
    super(itemView);
    tvTitle = itemView.findViewById(R.id.tv_item_title);
    imgPoster = itemView.findViewById(R.id.img_poster);
    tvDescription = itemView.findViewById(R.id.tv_item_description);
    tvDate = itemView.findViewById(R.id.tv_item_date);
    }

    void bind(CourseEntity course) {
    tvTitle.setText(course.getTitle());
    tvDescription.setText(course.getDescription());
    tvDate.setText(itemView.getResources().getString(R.string.deadline_date, course.getDeadline()));
    itemView.setOnClickListener(v -> {
    Intent intent = new Intent(itemView.getContext(), DetailCourseActivity.class);
    intent.putExtra(DetailCourseActivity.EXTRA_COURSE, course.getCourseId());
    itemView.getContext().startActivity(intent);
    });
    Glide.with(itemView.getContext())
    .load(course.getImagePath())
    .apply(RequestOptions.placeholderOf(R.drawable.ic_loading).error(R.drawable.ic_error))
    .into(imgPoster);
    }
    }
    }
    Pada kode di atas akan terjadi error pada bagian EXTRA_COURSE, klik ALT + Enter kemudian pilih:
    Kotlin
    2019121714133292b0d98e4bf996e5be9ac105d3b3f727.png
    Java
    201912171413552caa27ccb7656db4f0c7ece733118129.jpeg
    Setelah itu masukkan “extra_course” sebagai value-nya.
    Kotlin
    companion object {
    const val EXTRA_COURSE = "extra_course"
    }
    Java
    public static final String EXTRA_COURSE = "extra_course";
  2. Setelah Anda membuat kelas Adapter, tambahkan kode berikut di dalam AcademyFragment untuk menghubungkan fragment dengan RecyclerView.
    Kotlin
    class AcademyFragment : Fragment() {

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? =
    inflater.inflate(R.layout.fragment_academy, container, false)


    override fun onActivityCreated(savedInstanceState: Bundle?) {
    super.onActivityCreated(savedInstanceState)
    if (activity != null) {
    val courses = DataDummy.generateDummyCourses()
    val academyAdapter = AcademyAdapter()

    academyAdapter.setCourses(courses)

    with(rv_academy) {
    layoutManager = LinearLayoutManager(context)
    setHasFixedSize(true)
    adapter = academyAdapter
    }
    }
    }

    }
    Java
    public class AcademyFragment extends Fragment {
    private RecyclerView rvCourse;
    private ProgressBar progressBar;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    return inflater.inflate(R.layout.fragment_academy, container, false);
    }

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);
    rvCourse = view.findViewById(R.id.rv_academy);
    progressBar = view.findViewById(R.id.progress_bar);
    }

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    if (getActivity() != null) {
    List<CourseEntity> courses = DataDummy.generateDummyCourses();

    AcademyAdapter academyAdapter = new AcademyAdapter();
    academyAdapter.setCourses(courses);

    rvCourse.setLayoutManager(new LinearLayoutManager(getContext()));
    rvCourse.setHasFixedSize(true);
    rvCourse.setAdapter(academyAdapter);
    }
    }

    }
    Dengan begitu, kelas AcademyFragment sudah menampilkan data dari kelas DataDummy.
  3. Selanjutnya buatlah kembali kelas baru dan beri nama BookmarkAdapter di package bookmark.
    jxMR5ZbRYEhipS_XlgmggXrglKSB70ffL6oAq8HidgNNhCPnPLSssts9dlFSdTaDi3dx_yI1k_g7w0J3NNt8kxGilNoPOtOehgs-aqBH8y8Grj8l912QIvdTNvfAKOGJolTSLpsP
    Tambahkanlah kode berikut untuk setCourses, membuat kelas ViewHolder, mem-binding ViewHolder dan mengirim data ke DetailActivity.
    Kotlin
    class BookmarkAdapter(private val callback: BookmarkFragmentCallback) : RecyclerView.Adapter<BookmarkAdapter.CourseViewHolder>() {
    private val listCourses = ArrayList<CourseEntity>()

    fun setCourses(courses: List<CourseEntity>?) {
    if (courses == null) return
    listCourses.clear()
    listCourses.addAll(courses)
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CourseViewHolder {
    val view = LayoutInflater.from(parent.context).inflate(R.layout.items_bookmark, parent, false)
    return CourseViewHolder(view)
    }

    override fun onBindViewHolder(holder: CourseViewHolder, position: Int) {
    val course = listCourses[position]
    holder.bind(course)
    }

    override fun getItemCount(): Int = listCourses.size

    inner class CourseViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
    fun bind(course: CourseEntity){
    with(itemView){
    tv_item_title.text = course.title
    tv_item_description.text = course.description
    tv_item_date.text = resources.getString(R.string.deadline_date, course.deadline)
    setOnClickListener {
    val intent = Intent(context, DetailCourseActivity::class.java).apply {
    putExtra(DetailCourseActivity.EXTRA_COURSE, course.courseId)
    }
    context.startActivity(intent)
    }
    img_share.setOnClickListener { callback.onShareClick(course) }
    Glide.with(context)
    .load(course.imagePath)
    .apply(RequestOptions.placeholderOf(R.drawable.ic_loading)
    .error(R.drawable.ic_error))
    .into(img_poster)
    }
    }
    }
    }
    Java
    public class BookmarkAdapter extends RecyclerView.Adapter<BookmarkAdapter.CourseViewHolder> {
    private final BookmarkFragmentCallback callback;
    private ArrayList<CourseEntity> listCourses = new ArrayList<>();

    BookmarkAdapter(BookmarkFragmentCallback callback) {
    this.callback = callback;
    }

    void setCourses(List<CourseEntity> courses) {
    if (courses == null) return;
    listCourses.clear();
    listCourses.addAll(courses);
    }

    @NonNull
    @Override
    public CourseViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
    View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.items_bookmark, parent, false);
    return new CourseViewHolder(view);
    }

    @Override
    public void onBindViewHolder(@NonNull final CourseViewHolder holder, int position) {
    CourseEntity course = listCourses.get(position);
    holder.bind(course);
    }

    @Override
    public int getItemCount() {
    return listCourses.size();
    }

    class CourseViewHolder extends RecyclerView.ViewHolder {
    final TextView tvTitle;
    final TextView tvDescription;
    final TextView tvDate;
    final ImageButton imgShare;
    final ImageView imgPoster;

    CourseViewHolder(View itemView) {
    super(itemView);
    tvTitle = itemView.findViewById(R.id.tv_item_title);
    tvDescription = itemView.findViewById(R.id.tv_item_description);
    tvDate = itemView.findViewById(R.id.tv_item_date);
    imgShare = itemView.findViewById(R.id.img_share);
    imgPoster = itemView.findViewById(R.id.img_poster);
    }

    void bind(CourseEntity course) {
    tvTitle.setText(course.getTitle());
    tvDescription.setText(course.getDescription());
    tvDate.setText(itemView.getResources().getString(R.string.deadline_date, course.getDeadline()));
    itemView.setOnClickListener(v -> {
    Intent intent = new Intent(itemView.getContext(), DetailCourseActivity.class);
    intent.putExtra(DetailCourseActivity.EXTRA_COURSE, course.getCourseId());
    itemView.getContext().startActivity(intent);
    });
    imgShare.setOnClickListener(v -> callback.onShareClick(course));
    Glide.with(itemView.getContext())
    .load(course.getImagePath())
    .apply(RequestOptions.placeholderOf(R.drawable.ic_loading).error(R.drawable.ic_error))
    .into(imgPoster);
    }
    }
    }
    Pada kode di atas akan terjadi eror pada BookmarkFragmentCallback. Klik ALT + Enter kemudian pilih Create interface BookmarkFragmentCallback’.
    Kotlin
    20191217160115411c37d386620f225479953e9ca412a0.png
    Java
    20191217160008b7d22c18346e322e03cee8454f420ed9.jpeg
    Klik OK, maka secara otomatis akan ada kelas baru di dalam package bookmark.
    AAAy-WFSyiEnziHeQIhasJRVhGrM51GrBcag8iFO8sTCDuD7S80L_BvhAwFG2k-1HH_8ZJdXjEBo0TJhFV4dWO-nocw6eW4itXPI47RU7I8x5bMF0KO4753dTnptjKYLGPZN4fKv
    Setelah itu tambahkan kode berikut di kelas BookmarkFragmentCallback:
    Kotlin
    interface BookmarkFragmentCallback {
    fun onShareClick(course: CourseEntity)
    }
    Java
    interface BookmarkFragmentCallback {
    void onShareClick(CourseEntity course);
    }
  4. Setelah Anda membuat kelas Adapter, tambahkan kode berikut di dalam BookmarkFragment untuk menghubungkan Fragment dengan RecyclerView.
    Kotlin
    class BookmarkFragment : Fragment(), BookmarkFragmentCallback {

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
    savedInstanceState: Bundle?): View? {
    // Inflate the layout for this fragment
    return inflater.inflate(R.layout.fragment_bookmark, container, false)
    }

    override fun onActivityCreated(savedInstanceState: Bundle?) {
    super.onActivityCreated(savedInstanceState)
    if (activity != null) {
    val courses = DataDummy.generateDummyCourses()
    val adapter = BookmarkAdapter(this)

    adapter.setCourses(courses)

    with(rv_bookmark) {
    layoutManager = LinearLayoutManager(context)
    setHasFixedSize(true)
    this.adapter = adapter
    }
    }
    }

    override fun onShareClick(course: CourseEntity) {
    if (activity != null) {
    val mimeType = "text/plain"
    ShareCompat.IntentBuilder.from(activity).apply {
    setType(mimeType)
    setChooserTitle("Bagikan aplikasi ini sekarang.")
    setText(resources.getString(R.string.share_text, course.title))
    startChooser()
    }
    }
    }

    }
    Java
    public class BookmarkFragment extends Fragment implements BookmarkFragmentCallback {
    private RecyclerView rvBookmark;
    private ProgressBar progressBar;

    public BookmarkFragment() {
    // Required empty public constructor
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
    Bundle savedInstanceState) {
    // Inflate the layout for this fragment
    return inflater.inflate(R.layout.fragment_bookmark, container, false);
    }

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);
    rvBookmark = view.findViewById(R.id.rv_bookmark);
    progressBar = view.findViewById(R.id.progress_bar);
    }

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    if (getActivity() != null) {
    List<CourseEntity> courses = DataDummy.generateDummyCourses();

    BookmarkAdapter adapter = new BookmarkAdapter(this);
    adapter.setCourses(courses);

    rvBookmark.setLayoutManager(new LinearLayoutManager(getContext()));
    rvBookmark.setHasFixedSize(true);
    rvBookmark.setAdapter(adapter);
    }
    }

    @Override
    public void onShareClick(CourseEntity course) {
    if (getActivity() != null) {
    String mimeType = "text/plain";
    ShareCompat.IntentBuilder
    .from(getActivity())
    .setType(mimeType)
    .setChooserTitle("Bagikan aplikasi ini sekarang.")
    .setText(getResources().getString(R.string.share_text, course.getTitle()))
    .startChooser();
    }
    }

    }
    Dengan begitu, kelas BookmarkFragment sudah bisa menampilkan data dari kelas DataDummy.
  5. Selanjutnya, buatlah kembali kelas baru dan beri nama DetailCourseAdapter di package detail.
    yv8AmDT2z9Z4KcEZHC05RvMDWa5R69BmGnL-3tl6Vc-atEylqVHL8VRQK6XUxf6DjJrXM-DddyEarWzzwWmDz-9UzdOtnndIzqlwYGaAzrdf7xLrl8-vuMvdIEV95N78IOjfqtji
    Tambahkanlah kode berikut untuk setModules, membuat kelas ViewHolder, mem-binding ViewHolder dan mengirim data ke CourseReaderActivity.
    Kotlin
    class DetailCourseAdapter : RecyclerView.Adapter<DetailCourseAdapter.ModuleViewHolder>() {

    private val listModules = ArrayList<ModuleEntity>()

    fun setModules(modules: List<ModuleEntity>?) {
    if (modules == null) return
    listModules.clear()
    listModules.addAll(modules)
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ModuleViewHolder {
    val view = LayoutInflater.from(parent.context).inflate(R.layout.items_module_list, parent, false)
    return ModuleViewHolder(view)
    }

    override fun onBindViewHolder(viewHolder: ModuleViewHolder, position: Int) {
    val module = listModules[position]
    viewHolder.bind(module)
    }

    override fun getItemCount(): Int = listModules.size

    inner class ModuleViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
    fun bind(module: ModuleEntity) {
    with(itemView) {
    text_module_title.text = module.title
    }
    }
    }
    }
    Java
    public class DetailCourseAdapter extends RecyclerView.Adapter<DetailCourseAdapter.ModuleViewHolder> {

    private List<ModuleEntity> listModules = new ArrayList<>();

    void setModules(List<ModuleEntity> modules) {
    if (modules == null) return;
    listModules.clear();
    listModules.addAll(modules);
    }

    @NonNull
    @Override
    public ModuleViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
    View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.items_module_list, parent, false);
    return new ModuleViewHolder(view);
    }

    @Override
    public void onBindViewHolder(@NonNull ModuleViewHolder viewHolder, int position) {
    ModuleEntity module = listModules.get(position);
    viewHolder.bind(module);
    }

    @Override
    public int getItemCount() {
    return listModules.size();
    }

    class ModuleViewHolder extends RecyclerView.ViewHolder {
    final TextView textTitle;

    ModuleViewHolder(View itemView) {
    super(itemView);
    textTitle = itemView.findViewById(R.id.text_module_title);
    }

    void bind(ModuleEntity module) {
    textTitle.setText(module.getTitle());
    }
    }
    }
  6. Setelah Anda membuat kelas Adapter, tambahkan kode berikut di dalam DetailCourseActivity untuk menghubungkan Activity dengan RecyclerView.
    Kotlin
    class DetailCourseActivity : AppCompatActivity() {

    companion object {
    const val EXTRA_COURSE = "extra_course"
    }

    override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_detail_course)
    setSupportActionBar(toolbar)
    supportActionBar?.setDisplayHomeAsUpEnabled(true)

    val adapter = DetailCourseAdapter()

    val extras = intent.extras
    if (extras != null) {
    val courseId = extras.getString(EXTRA_COURSE)
    if (courseId != null) {
    val modules = DataDummy.generateDummyModules(courseId)
    adapter.setModules(modules)
    for(course in DataDummy.generateDummyCourses()) {
    if(course.courseId == courseId) {
    populateCourse(course)
    }
    }
    }
    }

    with(rv_module) {
    isNestedScrollingEnabled = false
    layoutManager = LinearLayoutManager(this@DetailCourseActivity)
    setHasFixedSize(true)
    this.adapter = adapter
    val dividerItemDecoration = DividerItemDecoration(rv_module.context, DividerItemDecoration.VERTICAL)
    addItemDecoration(dividerItemDecoration)
    }
    }

    private fun populateCourse(courseEntity: CourseEntity) {
    text_title.text = courseEntity.title
    text_desc.text = courseEntity.description
    text_date.text = resources.getString(R.string.deadline_date, courseEntity.deadline)

    Glide.with(this)
    .load(courseEntity.imagePath)
    .apply(RequestOptions.placeholderOf(R.drawable.ic_loading)
    .error(R.drawable.ic_error))
    .into(image_poster)

    btn_start.setOnClickListener {
    val intent = Intent(this@DetailCourseActivity, CourseReaderActivity::class.java).apply {
    putExtra(CourseReaderActivity.EXTRA_COURSE_ID, courseEntity.courseId)
    }
    startActivity(intent)
    }
    }
    }
    Java
    public class DetailCourseActivity extends AppCompatActivity {

    public static final String EXTRA_COURSE = "extra_course";
    private Button btnStart;
    private TextView textTitle;
    private TextView textDesc;
    private TextView textDate;
    private ImageView imagePoster;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_detail_course);
    Toolbar toolbar = findViewById(R.id.toolbar);
    setSupportActionBar(toolbar);
    if (getSupportActionBar() != null) {
    getSupportActionBar().setDisplayHomeAsUpEnabled(true);
    }

    btnStart = findViewById(R.id.btn_start);
    textTitle = findViewById(R.id.text_title);
    textDesc = findViewById(R.id.text_description);
    textDate = findViewById(R.id.text_date);
    RecyclerView rvModule = findViewById(R.id.rv_module);
    imagePoster = findViewById(R.id.image_poster);

    DetailCourseAdapter adapter = new DetailCourseAdapter();

    Bundle extras = getIntent().getExtras();
    if (extras != null) {
    String courseId = extras.getString(EXTRA_COURSE);
    if (courseId != null) {
    List<ModuleEntity> modules = DataDummy.generateDummyModules(courseId);
    adapter.setModules(modules);

    for (int i = 0; i < DataDummy.generateDummyCourses().size(); i++) {
    CourseEntity courseEntity = DataDummy.generateDummyCourses().get(i);
    if (courseEntity.getCourseId().equals(courseId)) {
    populateCourse(courseEntity);
    }
    }
    }
    }

    rvModule.setNestedScrollingEnabled(false);
    rvModule.setLayoutManager(new LinearLayoutManager(this));
    rvModule.setHasFixedSize(true);
    rvModule.setAdapter(adapter);
    DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(rvModule.getContext(), DividerItemDecoration.VERTICAL);
    rvModule.addItemDecoration(dividerItemDecoration);
    }

    private void populateCourse(CourseEntity courseEntity) {
    textTitle.setText(courseEntity.getTitle());
    textDesc.setText(courseEntity.getDescription());
    textDate.setText(getResources().getString(R.string.deadline_date, courseEntity.getDeadline()));

    Glide.with(this)
    .load(courseEntity.getImagePath())
    .apply(RequestOptions.placeholderOf(R.drawable.ic_loading)
    .error(R.drawable.ic_error))
    .into(imagePoster);

    btnStart.setOnClickListener(v -> {
    Intent intent = new Intent(DetailCourseActivity.this, CourseReaderActivity.class);
    intent.putExtra(CourseReaderActivity.EXTRA_COURSE_ID, courseEntity.getCourseId());
    startActivity(intent);
    });
    }
    }
    Pada kode di atas akan terjadi eror pada bagian EXTRA_COURSE_ID, klik ALT + Enter kemudian pilih Create constant field ‘EXTRA_COURSE_ID’ in ‘CourseReaderActivity’.
    Kotlin
    20191217161214b8d64d181a09e3430427096ca3b72eaa.png
    Java

    201912171610012c895dd78f09d9764bdea209660d2846.jpeg
    Setelah itu masukkan “extra_course_id” sebagai value-nya.
    Kotlin
    companion object {
    const val EXTRA_COURSE_ID = "extra_course_id"
    }
    Java
    public static final String EXTRA_COURSE_ID = "extra_course_id";
  7. Selanjutnya, buatlah kembali kelas baru dan beri nama SectionsPagerAdapter di package home. Kemudian tambahkan kode berikut:
    Kotlin
    class SectionsPagerAdapter(private val mContext: Context, fm: FragmentManager) : FragmentPagerAdapter(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {

    companion object {
    @StringRes
    private val TAB_TITLES = intArrayOf(R.string.home, R.string.bookmark)
    }

    override fun getItem(position: Int): Fragment =
    when (position) {
    0 -> AcademyFragment()
    1 -> BookmarkFragment()
    else -> Fragment()
    }

    override fun getPageTitle(position: Int): CharSequence? = mContext.resources.getString(TAB_TITLES[position])

    override fun getCount(): Int = 2

    }
    Java
    public class SectionsPagerAdapter extends FragmentPagerAdapter {

    @StringRes
    private static final int[] TAB_TITLES = new int[]{R.string.home, R.string.bookmark};
    private final Context mContext;

    SectionsPagerAdapter(Context context, FragmentManager fm) {
    super(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT);
    mContext = context;
    }

    @NonNull
    @Override
    public Fragment getItem(int position) {
    switch (position){
    case 0:
    return new AcademyFragment();
    case 1:
    return new BookmarkFragment();
    default:
    return new Fragment();
    }
    }

    @Nullable
    @Override
    public CharSequence getPageTitle(int position) {
    return mContext.getResources().getString(TAB_TITLES[position]);
    }

    @Override
    public int getCount() {
    return 2;
    }
    }
  8. Bukalah HomeActivity, hubungkan AcademyFragment dan BookmarkFragment dengan HomeActivity. Tambahkan kode berikut:
    Kotlin
    class HomeActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_home)

    val sectionsPagerAdapter = SectionsPagerAdapter(this, supportFragmentManager)
    view_pager.adapter = sectionsPagerAdapter
    tabs.setupWithViewPager(view_pager)

    supportActionBar?.elevation = 0f


    }
    }
    Java
    public class HomeActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_home);
    SectionsPagerAdapter sectionsPagerAdapter = new SectionsPagerAdapter(this, getSupportFragmentManager());
    ViewPager viewPager = findViewById(R.id.view_pager);
    viewPager.setAdapter(sectionsPagerAdapter);
    TabLayout tabs = findViewById(R.id.tabs);
    tabs.setupWithViewPager(viewPager);

    if (getSupportActionBar() != null) {
    getSupportActionBar().setElevation(0);
    }

    }
    }
  9. Anda sudah membuat halaman Academy, Bookmark dan Detail Course. Sampai sini Anda bisa mencobanya dahulu. Langkah selanjunya, Anda akan membuat halaman untuk list Module dan Detail Module. Sebelum itu, tambahkan sebuah kelas baru di package reader.
    uZt5IMIA_f4YQnQnXaK_AbWVc47RcyYKU04jZtTzMhgOddQwFFGI3VBp2Y1B-hYh7LbmGz_5vqQTeCXyK8gOshJsaCvQn6JmF_FSRNljfDmYP0ZPZStvTY2TsFHxhBnyNCw-DmvF
    Setelah itu tambah kode berikut:
    Kotlin
    interface CourseReaderCallback {
    fun moveTo(position: Int, moduleId: String)
    }
    Java
    public interface CourseReaderCallback {
    void moveTo(int position, String moduleId);
    }
    CourseReaderCallback nantinya akan digunakan untuk pindah dari halaman satu ke halaman lain.
  10. Bukalah ModuleContentFragment dan ubah kode di dalamnya untuk menampilkan data dummy di WebView.
    Kotlin
    class ModuleContentFragment : Fragment() {

    companion object {
    val TAG = ModuleContentFragment::class.java.simpleName

    fun newInstance(): ModuleContentFragment {
    return ModuleContentFragment()
    }
    }

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
    savedInstanceState: Bundle?): View? {
    // Inflate the layout for this fragment
    return inflater.inflate(R.layout.fragment_module_content, container, false)
    }

    override fun onActivityCreated(savedInstanceState: Bundle?) {
    super.onActivityCreated(savedInstanceState)
    if (activity != null) {
    val content = ContentEntity("<h3 class=\\\"fr-text-bordered\\\">Contoh Content</h3><p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>")
    populateWebView(content)
    }
    }

    private fun populateWebView(content: ContentEntity) {
    web_view.loadData(content.content, "text/html", "UTF-8")
    }

    }
    Java
    public class ModuleContentFragment extends Fragment {
    public static final String TAG = ModuleContentFragment.class.getSimpleName();

    private WebView webView;

    public ModuleContentFragment() {
    // Required empty public constructor
    }

    public static ModuleContentFragment newInstance() {
    return new ModuleContentFragment();
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
    Bundle savedInstanceState) {
    // Inflate the layout for this fragment
    return inflater.inflate(R.layout.fragment_module_content, container, false);
    }

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);
    webView = view.findViewById(R.id.web_view);
    }

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    if (getActivity() != null) {
    ContentEntity content = new ContentEntity("<h3 class=\\\"fr-text-bordered\\\">Contoh Content</h3><p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>");
    populateWebView(content);
    }
    }

    private void populateWebView(ContentEntity content) {
    webView.loadData(content.getContent(), "text/html", "UTF-8");
    }

    }
    Dengan begitu, kelas ModuleContentFragment sudah bisa menampilkan data dummy di WebView.
  11. Sebelum masuk ke ModuleListFragment, buatlah kembali kelas adapter baru dan beri nama ModuleListAdapter di package reader.list.
    ojxrzYOWgSoAQGmMdFI1RHoz-jv8BEImNSDo8c2edItXGrc8K9HD0V_5kdkIhnF73Us9GBqnmoK4_7LKePEzPJh4Q8d7gvrEvqXMGDmSXLrjobr-A72YrdAQaM-H_0ViVmvrD0RU
    Selanjutnya, ubahlah kode pada kelas tersebut:
    Kotlin
    class ModuleListAdapter internal constructor(private val listener: MyAdapterClickListener) : RecyclerView.Adapter<ModuleListAdapter.ModuleViewHolder>() {
    private val listModules = ArrayList<ModuleEntity>()

    internal fun setModules(modules: List<ModuleEntity>?) {
    if (modules == null) return
    listModules.clear()
    listModules.addAll(modules)
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ModuleViewHolder {
    val view = LayoutInflater.from(parent.context).inflate(R.layout.items_module_list_custom, parent, false)
    return ModuleViewHolder(view)
    }

    override fun onBindViewHolder(viewHolder: ModuleViewHolder, position: Int) {
    val module = listModules[position]
    viewHolder.bind(module)
    viewHolder.itemView.setOnClickListener {
    listener.onItemClicked(viewHolder.adapterPosition, listModules[viewHolder.adapterPosition].moduleId)
    }
    }

    override fun getItemCount(): Int = listModules.size

    inner class ModuleViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
    private val textTitle: TextView = itemView.findViewById(R.id.text_module_title)
    fun bind(module: ModuleEntity) {
    textTitle.text = module.title
    }
    }
    }

    internal interface MyAdapterClickListener {
    fun onItemClicked(position: Int, moduleId: String)
    }
    Java
    public class ModuleListAdapter extends RecyclerView.Adapter<ModuleListAdapter.ModuleViewHolder> {

    private final MyAdapterClickListener listener;
    private List<ModuleEntity> listModules = new ArrayList<>();

    ModuleListAdapter(MyAdapterClickListener listener) {
    this.listener = listener;
    }

    void setModules(List<ModuleEntity> listModules) {
    if (listModules == null) return;
    listModules.clear();
    listModules.addAll(listModules);
    }

    @NonNull
    @Override
    public ModuleViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
    return new ModuleViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.items_module_list_custom, parent, false));
    }

    @Override
    public void onBindViewHolder(ModuleViewHolder viewHolder, int position) {
    ModuleEntity module = listModules.get(position);
    viewHolder.bind(module);
    viewHolder.itemView.setOnClickListener(v ->
    listener.onItemClicked(viewHolder.getAdapterPosition(), listModules.get(viewHolder.getAdapterPosition()).getModuleId())
    );
    }

    @Override
    public int getItemCount() {
    return listModules.size();
    }

    class ModuleViewHolder extends RecyclerView.ViewHolder {
    final TextView textTitle;

    ModuleViewHolder(View itemView) {
    super(itemView);
    textTitle = itemView.findViewById(R.id.text_module_title);
    }

    void bind(ModuleEntity module) {
    textTitle.setText(module.getTitle());
    }
    }
    }

    interface MyAdapterClickListener {
    void onItemClicked(int position, String moduleId);
    }
    Pada kelas CourseReaderAdapter terdapat sebuah interface MyAdapterClickListener yang nantinya digunakan untuk berpindah ke halaman ModuleContentFragment.
  12. Setelah Anda membuat kelas Adapter, tambahkan kode berikut di dalam ModuleListFragment untuk menghubungkan Fragment dengan RecyclerView.
    Kotlin
    class ModuleListFragment : Fragment(), MyAdapterClickListener {

    companion object {
    val TAG = ModuleListFragment::class.java.simpleName

    fun newInstance(): ModuleListFragment = ModuleListFragment()
    }

    private lateinit var adapter: ModuleListAdapter
    private lateinit var courseReaderCallback: CourseReaderCallback

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
    savedInstanceState: Bundle?): View? {
    // Inflate the layout for this fragment
    return inflater.inflate(R.layout.fragment_module_list, container, false)
    }

    override fun onActivityCreated(savedInstanceState: Bundle?) {
    super.onActivityCreated(savedInstanceState)
    adapter = ModuleListAdapter(this)
    populateRecyclerView(DataDummy.generateDummyModules("a14"))
    }

    override fun onAttach(context: Context) {
    super.onAttach(context)
    courseReaderCallback = context as CourseReaderActivity
    }

    override fun onItemClicked(position: Int, moduleId: String) {
    courseReaderCallback.moveTo(position, moduleId)
    }

    private fun populateRecyclerView(modules: List<ModuleEntity>) {
    progress_bar.visibility = View.GONE
    adapter.setModules(modules)
    with(rv_module) {
    layoutManager = LinearLayoutManager(context)
    setHasFixedSize(true)
    adapter = adapter
    }
    val dividerItemDecoration = DividerItemDecoration(rv_module.context, DividerItemDecoration.VERTICAL)
    rv_module.addItemDecoration(dividerItemDecoration)
    }
    }
    Java
    public class ModuleListFragment extends Fragment implements MyAdapterClickListener {

    public static final String TAG = ModuleListFragment.class.getSimpleName();
    private ModuleListAdapter adapter;
    private CourseReaderCallback courseReaderCallback;
    private RecyclerView recyclerView;
    private ProgressBar progressBar;

    public ModuleListFragment() {
    // Required empty public constructor
    }

    public static ModuleListFragment newInstance() {
    return new ModuleListFragment();
    }


    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
    Bundle savedInstanceState) {
    // Inflate the layout for this fragment
    return inflater.inflate(R.layout.fragment_module_list, container, false);
    }

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);
    recyclerView = view.findViewById(R.id.rv_module);
    progressBar = view.findViewById(R.id.progress_bar);
    }

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    if (getActivity() != null) {
    adapter = new ModuleListAdapter(this);
    populateRecyclerView(DataDummy.generateDummyModules("a14"));
    }

    }

    @Override
    public void onAttach(@NonNull Context context) {
    super.onAttach(context);
    courseReaderCallback = ((CourseReaderActivity) context);
    }

    @Override
    public void onItemClicked(int position, String moduleId) {
    courseReaderCallback.moveTo(position, moduleId);
    }

    private void populateRecyclerView(List<ModuleEntity> modules) {
    progressBar.setVisibility(View.GONE);
    adapter.setModules(modules);
    recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
    recyclerView.setHasFixedSize(true);
    recyclerView.setAdapter(adapter);
    DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(recyclerView.getContext(), DividerItemDecoration.VERTICAL);
    recyclerView.addItemDecoration(dividerItemDecoration);
    }
    }
  13. Ketika Anda memanggil generateDummyModules(), variable “a14” adalah sampel inputan data academy. Nanti akan kita ubah sesuai dengan posisi Fragment yang akan diklik.
    Dan dengan menambahkan kode di atas, maka akan terjadi eror dibagian kode ini:
    Kotlin
    courseReaderCallback = context as CourseReaderActivity
    Java
    courseReaderCallback = ((CourseReaderActivity) context);
    Ini terjadi karena CourseReaderActivity belum mengimplementasikan CourseReaderCallback.
  14. Bukalah CourseReaderActivity, hubungkan ModuleContentFragment dan ModuleListFragment dengan CourseReaderActivity.
    Kotlin
    class CourseReaderActivity : AppCompatActivity(), CourseReaderCallback {

    companion object {
    const val EXTRA_COURSE_ID = "extra_course_id"
    }

    override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_course_reader)

    val bundle = intent.extras
    if (bundle != null) {
    val courseId = bundle.getString(EXTRA_COURSE_ID)
    if (courseId != null) {
    populateFragment()
    }
    }
    }

    override fun moveTo(position: Int, moduleId: String) {
    val fragment = ModuleContentFragment.newInstance()
    supportFragmentManager.beginTransaction().add(R.id.frame_container, fragment, ModuleContentFragment.TAG)
    .addToBackStack(null)
    .commit()
    }

    override fun onBackPressed() {
    if (supportFragmentManager.backStackEntryCount <= 1) {
    finish()
    } else {
    super.onBackPressed()
    }
    }

    private fun populateFragment() {
    val fragmentTransaction = supportFragmentManager.beginTransaction()
    var fragment = supportFragmentManager.findFragmentByTag(ModuleListFragment.TAG)
    if (fragment == null) {
    fragment = ModuleListFragment.newInstance()
    fragmentTransaction.add(R.id.frame_container, fragment, ModuleListFragment.TAG)
    fragmentTransaction.addToBackStack(null)
    }
    fragmentTransaction.commit()
    }
    }
    Java
    public class CourseReaderActivity extends AppCompatActivity implements CourseReaderCallback {

    public static final String EXTRA_COURSE_ID = "extra_course_id";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_course_reader);
    CourseReaderViewModel viewModel = new ViewModelProvider(this, new ViewModelProvider.NewInstanceFactory()).get(CourseReaderViewModel.class);

    Bundle bundle = getIntent().getExtras();
    if (bundle != null) {
    String courseId = bundle.getString(EXTRA_COURSE_ID);
    if (courseId != null) {
    viewModel.setCourseId(courseId);
    populateFragment();
    }
    }
    }

    @Override
    public void moveTo(int position, String moduleId) {
    Fragment fragment = ModuleContentFragment.newInstance();
    getSupportFragmentManager().beginTransaction().add(R.id.frame_container, fragment, ModuleContentFragment.TAG)
    .addToBackStack(null)
    .commit();
    }

    @Override
    public void onBackPressed() {
    if (getSupportFragmentManager().getBackStackEntryCount() <= 1) {
    finish();
    } else {
    super.onBackPressed();
    }
    }

    private void populateFragment() {
    FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
    Fragment fragment = getSupportFragmentManager().findFragmentByTag(ModuleListFragment.TAG);
    if (fragment == null) {
    fragment = ModuleListFragment.newInstance();
    fragmentTransaction.add(R.id.frame_container, fragment, ModuleListFragment.TAG);
    fragmentTransaction.addToBackStack(null);
    }
    fragmentTransaction.commit();
    }
    }
    Metode moveTo digunakan untuk memanggil ModuleContentFragment sesuai dengan posisi dan moduleId yang dipilih.

  15. Inilah saat yang ditunggu-tunggu. Kita akan menjalankan aplikasi. Silakan jalankan aplikasi yang Anda buat, hasilnya akan seperti ini:201912171702204eec34888068aa412dcc54b05f22996e.gif

Bedah Kode

Proyek Academy ini terdiri dari 3 Activity yakni HomeActivity, DetailCourseActivity dan CourseReaderActivity. Dalam proyek ini juga mempunyai 4 Fragment yakni AcademyFragment, BookmarkFragment, ModuleListFragment dan ModuleContentFragment. Secara fungsi atau kegunaan akan jadi seperti ini:
  • HomeActivity: Menampilkan 2 Fragment (AcademyFragment dan BookmarkFragment) dan sebagai halaman utama dari Aplikasi.
  • DetailCourseActivity: Menampilkan detail Course dan menampilkan list Module yang ada tiap Course-nya. Selain itu di halaman ini juga nantinya akan ada tombol bookmark untuk menyimpan course yang Anda suka.
  • CourseReader: Menampilkan 2 Fragment(ModuleListFragment dan ModuleContentFragment) dan di Activity ini nanti akan ada 2 tampilan yakni untuk ukuran layar yang besar dan kecil.
  • AcademyFragment: Digunakan untuk menampilkan semua Course.
  • BookmarkFragment: Digunakan untuk menampilkan semua Course yang sudah Anda bookmark.
  • ModuleListFragment: Digunakan untuk menampilkan semua Module sesuai Course yang dipilih.
  • ModuleContentFragment: Menampilkan Content dari Module yang dipilih.

Selain itu, proyek Academy nantinya akan menerapkan berbagai komponen Android Jetpack seperti ViewModel, LiveData, Room dan Pagination. Selain itu ada materi pendukung juga seperti Repository, IdleResource, UnitTesting, InstrumentalTest, dll. Tentunya materi akan dibahas secara bertahap. Yang paling keren, proyek Academy akan menggunakan prinsip Offline-Online sehingga ketika data tidak ada di local, maka akan request data dari network dan semua data akan disimpan ke local pada saat itu juga.
Jika Anda kesulitan untuk melakukan langkah-langkah pada latihan kali ini, Anda bisa unduh proyek starter-nya di sini.