Showing posts with label Android. Show all posts
Showing posts with label Android. Show all posts

Monday, August 4, 2025

🎥 Cara Membuat Foto Menjadi Video Menggunakan Gemini AI


Ingin mengubah foto biasa menjadi video menarik hanya dengan beberapa klik? Gemini AI hadir sebagai solusi canggih yang dapat mengubah gambar menjadi video berkualitas tinggi dengan efek visual, gerakan, dan transisi otomatis. Cocok untuk konten media sosial, promosi produk, portofolio, hingga dokumentasi pribadi.


✅ Apa Itu Gemini AI?


Gemini AI adalah platform kecerdasan buatan terbaru dari Google yang mampu membuat video, teks, gambar, bahkan aplikasi. Dengan teknologi generatif AI, Gemini dapat menghidupkan foto menjadi video sinematik yang estetis, lengkap dengan animasi wajah, gerakan tubuh, dan latar belakang yang dinamis.



---


🔧 Langkah-Langkah Membuat Foto Menjadi Video dengan Gemini AI


1. Buka Aplikasi Gemini AI


Kunjungi https://gemini.google.com atau buka aplikasi Gemini (jika sudah tersedia di Android atau iOS).


Login dengan akun Google kamu.



2. Masukkan Perintah (Prompt)


Ketik perintah seperti:


> “Ubah foto ini menjadi video berdurasi 5 detik, buat efek slow motion, background pantai saat sunset.”




Jika kamu ingin, gunakan prompt dalam bahasa Inggris untuk hasil lebih optimal.



3. Upload Foto


Klik ikon “Upload” atau seret foto dari perangkat kamu ke area yang disediakan.


Gemini akan memprosesnya dan mulai membuat video otomatis.



4. Tunggu Proses Rendering


Biasanya hanya memakan waktu 1–2 menit.


Kamu akan melihat pratinjau video sebelum menyimpan.



5. Unduh atau Bagikan


Klik Download untuk menyimpan video.


Atau langsung bagikan ke TikTok, Instagram, YouTube Shorts, dll.




---


💡 Tips Prompt Terbaik Agar Video Lebih Menarik


✅ Gunakan kata kerja visual: walking, smiling, dancing, turning, looking at camera.


✅ Tambahkan detail suasana: golden hour, rainy street, snowy mountain, neon city night.


✅ Contoh prompt:


> “Create a video from this photo: man walking slowly on a rainy street at night, cinematic style.”






---


🎯 Kelebihan Menggunakan Gemini AI


✔️ Tidak perlu skill editing


✔️ Proses cepat dan otomatis


✔️ Cocok untuk semua jenis konten


✔️ Didukung teknologi AI tercanggih dari Google




---


🔎 Kata Kunci SEO Pendukung


  • Cara membuat foto jadi video otomatis
  • Ubah gambar jadi video dengan AI
  • Edit foto jadi video sinematik
  • Gemini AI ubah foto ke video
  • Aplikasi AI untuk video dari foto
  • Google Gemini ubah gambar ke video
  • AI animasi wajah dari foto
  • Video AI dari gambar




---


🔚 Kesimpulan


Dengan Gemini AI, membuat video dari foto kini sangat mudah, cepat, dan hasilnya profesional. Cukup upload foto, masukkan perintah, dan video langsung jadi. Teknologi AI ini sangat cocok untuk content creator, marketer, dan siapa saja yang ingin menghasilkan konten visual yang memukau tanpa ribet.



Friday, October 14, 2022

Gunakan Kali linux Di Android tanpa root



Cara termudah untuk menginstal kali-linux di android menggunakan termux. Anda tidak perlu ponsel yang di-rooting . anda juga dapat menggunakan sudo (jalankan perintah sebagai pengguna super)

kuti langkah-langkah yang diberikan ini:


Unduh Termux dari play store terlebih dahulu: https://play.google.com/store/apps/details?id=com.termux


  1. buka termux di playstore
  2. Jenis: termux-setup-storage 
  3. Izinkan izin untuk Mengakses penyimpanan
  4. lalu ketik: pkg install wget
  5. jika dikatakan tidak ditemukan atau dalam mode pemeliharaan, ubah repo
  6. untuk mengubah jenis repo: termux-change-repo
  7. pilih semua 3 dan kemudian klik ok
  8. lalu pilih mirror yang dihosting termux dan klik ok
  9. lalu ketik: pkg install wget
  10. jika masih mengatakan  tidak ditemukan atau dalam mode pemeliharaan   ubah repo lagi dan pilih cermin lain
  11. jika wget terinstal ketik: wget -O install-nethunter-termux https://offs.ec/2MceZWr
  12. sekarang ketik: chmod +x install-nethunter-termux
  13. setelah itu ketik: ./install-nethunter-termux
  14. Sekarang Anda dapat menggunakan kali linux CLI
  15. Tipe: nethunter 

  16. Anda juga dapat menjalankan dengan sudo (pengguna super)

Jalankan Kali linux di Android dengan GUI:

> Unduh toko Nethunter: https://store.nethunter.com/en/

> Unduh Selanjutnya : Nethunter Kex dari toko

> Setelah itu buka T ermux dan ketik: nethunter kex

> atur kata sandi lalu catat kata sandi dan portnya

dan kemudian buka Aplikasi Nethunter Kex


masukkan kata sandi & port yang sama dan klik Connect


jika mengalami masalah hubungi kami di kolom komentar

Tuesday, June 16, 2020

Tutorial Cara Deployment (Build APK & IPA) Aplikasi Android Atau iOS Di Flutter

Tutorial Cara Deployment Build APK Atau IPA Di Flutter

Setelah melalui tahapan pengembangan aplikasi, salah satu tahapan terakhir yang perlu dilakukan adalah deployment

Tahapan ini dimulai dari proses build yang membungkus project aplikasi kita menjadi berkas yang bisa dieksekusi pada masing-masing platform, misalnya format APK (Android Application Package) atau AAB (Android App Bundle) untuk Android dan IPA (iOS App Store Package) untuk iOS. Berkas ini yang nantinya akan didistribusikan ke Google Play Store atau pun App Store.
Jika Anda belum memahami berkas APK atau IPA, Anda dapat menyamakannya dengan berkas EXE (Executable) pada sistem operasi Windows.

Build APK

Project Flutter yang telah dibuat dapat kita build menjadi berkas .apk yang dapat berjalan di Android. Build APK adalah suatu proses membungkus aplikasi flutter menjadi format .apk yang nantinya untuk diinstal pada perangkat Android. Berikut tahapan ketika build aplikasi Flutter ke APK.

AndroidManifest.xml

Sebelum mem-build APK, kita akan mengatur berkas android/app/src/main/AndroidManifest.xmlAndroidManifest.xml merupakan sebuah berkas yang berisikan informasi mengenai aplikasi Android yang akan di-build.
Informasi-informasi tersebut berupa nama aplikasi, ikon, permissionscreen orientation, dan lain-lain. Isi dari AndroidManifest.xml seperti berikut:
  1. <manifest xmlns:android="http://schemas.android.com/apk/res/android"
  2.     package="id.eudeka.samples">
  3.     <application
  4.         android:name="io.flutter.app.FlutterApplication"
  5.         android:label="samples"
  6.         android:icon="@mipmap/ic_launcher">
  7.         <activity
  8.             android:name=".MainActivity"
  9.             android:launchMode="singleTop"
  10.             android:theme="@style/LaunchTheme"
  11.             android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
  12.             android:hardwareAccelerated="true"
  13.             android:windowSoftInputMode="adjustResize">
  14.             <meta-data
  15.               android:name="io.flutter.embedding.android.NormalTheme"
  16.               android:resource="@style/NormalTheme"
  17.               />
  18.             <meta-data
  19.               android:name="io.flutter.embedding.android.SplashScreenDrawable"
  20.               android:resource="@drawable/launch_background"
  21.               />
  22.             <intent-filter>
  23.                 <action android:name="android.intent.action.MAIN"/>
  24.                 <category android:name="android.intent.category.LAUNCHER"/>
  25.             </intent-filter>
  26.         </activity>
  27.         <meta-data
  28.             android:name="flutterEmbedding"
  29.             android:value="2" />
  30.     </application>
  31. </manifest>

Setting Nama Aplikasi

Untuk mengatur nama aplikasi, kita cukup mengubah properti android:label yang ada pada file AndroidManifest.xml seperti berikut:


  1. <application

  2.         android:name="io.flutter.app.FlutterApplication"

  3.         android:label="common_widget"

  4.         android:icon="@mipmap/ic_launcher">


menjadi


  1. <application

  2.         android:name="io.flutter.app.FlutterApplication"

  3.         android:label="Nama Aplikasi"

  4.         android:icon="@mipmap/ic_launcher">


Isikan android:label dengan nama aplikasi yang diinginkan. Atau Anda bisa gunakan library berikut untuk menghasilkan nama aplikasi dari pubspec.yaml.

Setting Ikon Aplikasi

Secara default ikon aplikasi Flutter kita adalah ikon Flutter. Untuk mengubah icon aplikasi dengan mudah, kita akan mengganti gambar ic_launcher.png yang berada pada folder android/app/src/main/res/ yang terbagi menjadi berbagai mipmap (ukuran resolusi ikon).
Hal yang pertama kita lakukan adalah membuat ikon aplikasi dengan Android Asset Studio.
202006121504052cad59b5a36431f3ec30eca895fcd433.jpeg 
Dengan Android Asset Studio, kita dapat membuat ikon aplikasi dengan mudah dan nantinya akan terbuat dalam berbagai resolusi (mipmap). Setelah membuat ikon sesuai dengan keinginan, tekan tombol download yang ada di kanan atas.
20200612150522ed44a921a5b5ac5ab9c91582bdc45b49.jpeg 
Setelah mengunduh, unzip-lah berkas tersebut dan temukan folder res/ di dalamnya. Lalu copy folder res/ ke android/app/src/main/res/ untuk mengganti ic_launcher.png pada setiap mipmap dengan ikon aplikasi yang baru. Atau Anda bisa gunakan library berikut untuk menghasilkan icon launcher dari pubspec.yaml.

Setting Perizinan Aplikasi

Ketika aplikasi dalam mode debug atau profil, perizinan internet akan secara otomatis ditambahkan. Namun ketika Anda ingin menjalankan atau membuatnya dalam mode rilis, Anda perlu menambahkan semua perizinan yang dibutuhkan pada AndroidManifest.
Untuk menambahkan perizinan pada aplikasi Android, Anda bisa menambahkan tag uses-permission pada AndroidManifest, di dalam tag manifest dan sejajar tag application. Contohnya seperti di bawah ini:


  1. <uses-permission android:name="android.permission.INTERNET"/>



Melakukan Build APK

Setelah kita mengatur nama dan ikon aplikasi, langkah selanjutnya adalah melakukan build aplikasi menjadi APK. 
Sebelumnya terdapat tiga (3) jenis mode aplikasi yang perlu diketahui, yaitu debugprofile, dan release. APK debug umumnya digunakan untuk pengujian dan penggunaan aplikasi secara internal. 
Mode debug digunakan secara default ketika menjalankan aplikasi menggunakan perintah flutter run
Sementara untuk bisa dirilis melalui Google Play Store, Anda perlu membuat APK release. Sedangkan mode profile sama hal nya dengan release hanya saja tetap dapat di-debug menggunakan tools seperti DevTools dan tidak dapat dijalankan di emulator atau simulator.
Pada kelas ini kita akan mempelajari bagaimana membuat APK debug. Caranya ialah menggunakan terminal pada Android Studio. Tekan tombol Terminal yang ada pada pojok kiri bawah.
20200612151835f8ac5893630852aeb9d0951b22027fbb.jpeg 
Bila menggunakan Visual Studio Code pilih menu terminal yang ada pada menu kiri atas. Lalu pilih new terminal.

2020061215195144ffaf099559914ad682bb50742a337e.jpeg 








Jika terminal telah muncul, tuliskan perintah berikut:


  1. flutter build apk --debug





Tunggu hingga proses build berhasil. Setelah berhasil, hasil build yang berupa berkas apk-debug.apk akan terletak di folder build/app/outputs/apk/debug/ atau akan muncul direktori tempat tersimpannya berkas ketika proses build selesai pada Terminal.
Untuk bisa mem-build apk release dan mengunggahnya melalui Google Play Store, Anda memerlukan signing keySigning key ini digunakan sebagai tanda tangan supaya aplikasi Anda lebih aman. Secara default Flutter menggunakan debug key sebagai signing key sehingga Anda sebenarnya bisa membuat apk release dengan menjalankan perintah berikut:


  1. flutter build apk





Namun, tentunya akan lebih baik jika Anda menggunakan signing key milik Anda sendiri. 
Cara untuk membuat signing key dan membuat apk release dapat Anda baca pada tautan dokumentasi berikut: https://flutter.dev/docs/deployment/android.

Build IPA

Catatan: Build .IPA hanya bisa dijalankan dengan mendaftar akun Apple Developer Program. Silakan baca informasi tentang Apple Developer Program di sini https://developer.apple.com/programs/.
Untuk lulus dari kelas ini, tidak diwajibkan untuk bisa build .IPA.
Pada materi ini kita akan mempelajari bagaimana melakukan build aplikasi Flutter menjadi berkas .ipa yang dapat dijalankan pada perangkat iOS. Sebelum melakukan build, ada beberapa hal yang perlu kita atur seperti nama dan ikon aplikasi.

Setting Nama Aplikasi

Untuk mengatur nama aplikasi buka berkas Info.plist pada direktori /ios/Runner/. Konfigurasi untuk nama aplikasi dapat Anda temukan dan ubah pada key Bundle Name.


  1. <key>CFBundleName</key>

  2. <string>builder</string>


Atau Anda bisa gunakan library berikut untuk men-generate nama aplikasi melalui pubspec.yaml.

Setting Ikon Aplikasi

Sama seperti perangkat Android, layar untuk perangkat iPhone juga terbagi ke dalam berbagai ukuran. Sehingga diperlukan juga ukuran ikon yang berbeda. Untuk membuat berbagai ukuran ikon untuk iOS, Anda dapat memanfaatkan website seperti https://appicon.co/ atau yang lainnya. Website ini juga bisa digunakan untuk membuat ikon aplikasi Android. Perlu Anda perhatikan bahwa untuk icon pada iOS Anda tidak dapat membuatnya transparant. Alih-alih, Anda harus membuat icon tersebut penuh dengan warna 
20200615175755d214ec5285ddf91c7ed4c8c2d794c17e.jpeg
Ketika Anda klik Generate, browser akan mengunduh berkas yang Anda butuhkan. Ikon untuk aplikasi iOS bisa Anda dapatkan pada folder Assets.xcassets.
LU9_iLjbu4Fa3rUc7WQdj9VlgisQorwF5DslXuJbKU2fHDJWE3O6HcUh0VDmberZQqbRItq4kS3N6FF5Ejqj3LkFS5EujfYHMYIuNY_F0OIjXU0SYfxv7ShYlBf3BPBK7QPIztJJ
Selanjutnya Anda dapat mengganti folder Assets.xcassets yang ada pada direktori /ios/Runner/ dengan hasil ikon yang sudah Anda generate. Atau Anda bisa menggunakan library berikut untuk men-generate ikon aplikasi melalui pubspec.yaml.

Melakukan build IPA

File IPA juga terbagi menjadi debugprofile, dan release. Namun untuk melakukan build aplikasi Flutter menjadi IPA hanya bisa dilakukan pada device macOS. Untuk melakukan build aplikasi menjadi .ipa Anda cukup membuka terminal pada editor atau IDE Anda lalu menjalankan perintah berikut:


  1. flutter build ios





Secara default perintah di atas akan menghasilkan ipa release, sedangkan jika Anda ingin membuat versi debug atau profile, gunakan perintah:


  1. flutter build ios --debug




Namun, bagi Anda yang tidak mempunyai perangkat Apple jangan khawatir, Anda tetap dapat men-deploy project Anda ke iOS menggunakan CI/CD seperti Codemagic dan Bitrise.
Selamat Anda telah berhasil membuat dan build project Flutter di Android atau iOS.

Monday, June 15, 2020

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.