Cara Mengirim Data Antar Fragment Pengembangan Aplikasi Android



Kita telah belajar bagaimana berpindah dari satu fragment ke fragment lain. Nah, kurang lengkap rasanya kalau belum tahu bagaimana berpindah fragment dengan membawa data. Terdapat dua cara yakni dengan menggunakan obyek bundle dan setter and getter. Mari langsung praktikkan kedua cara tersebut.

  1. Buat fragment lagi dengan nama DetailCategoryFragment.
    201811131304416d6b0e98b7cdc938c181bb81b926e6f2
  2. Pada fragment_detail_category sesuaikan kodenya menjadi seperti berikut:
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="16dp">

    <TextView
    android:id="@+id/tv_category_name"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginBottom="16dp"
    android:text="@string/category_name" />

    <TextView
    android:id="@+id/tv_category_description"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginBottom="16dp"
    android:text="@string/category_description" />

    <Button
    android:id="@+id/btn_profile"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="@string/to_profile" />

    <Button
    android:id="@+id/btn_show_dialog"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="@string/show_dialog" />

    </LinearLayout>
  3. Setelah selesai, silakan lengkapi kode pada DetailCategoryFragment. Pertama, tambahkan beberapa view, dan juga casting-nya.
    Kotlin
    class DetailCategoryFragment : Fragment(){

    lateinit var tvCategoryName: TextView
    lateinit var tvCategoryDescription: TextView
    lateinit var btnProfile: Button
    lateinit var btnShowDialog: Button


    ...

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    tvCategoryName = view.findViewById(R.id.tv_category_name)
    tvCategoryDescription = view.findViewById(R.id.tv_category_description)
    btnProfile = view.findViewById(R.id.btn_profile)
    btnProfile.setOnClickListener(this)
    btnShowDialog = view.findViewById(R.id.btn_show_dialog)
    btnShowDialog.setOnClickListener(this)

    }
    }
    Java
        TextView tvCategoryName;
    TextView tvCategoryDescription;
    Button btnProfile;
    Button btnShowDialog;


    ...

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);
    tvCategoryName = view.findViewById(R.id.tv_category_name);
    tvCategoryDescription = view.findViewById(R.id.tv_category_description);
    btnProfile = view.findViewById(R.id.btn_profile);
    btnProfile.setOnClickListener(this);
    btnShowDialog = view.findViewById(R.id.btn_show_dialog);
    btnShowDialog.setOnClickListener(this);

    }
    Kemudian metode setOnClickListener akan mengalami error karena Anda belum mengimplementasi onClick() ke DetailCategoryFragment. Maka implementasikanlah onClick di kelas fragment-nya dan juga set listener di view-nya.
    Kotlin
    class DetailCategoryFragment : Fragment(), View.OnClickListener {

    ...

    override fun onClick(v: View) {
    when (v.id) {

    }
    }


    }
    Java
    public class DetailCategoryFragment extends Fragment implements View.OnClickListener {

    ...

    @Override
    public void onClick(View v) {
    switch (v.getId()) {

    }
    }

    }
    Dan tambahkan beberapa variabel dan berikan aksi ketika button diklik.
    Kotlin
    class DetailCategoryFragment : Fragment(), View.OnClickListener {

    var description: String? = null

    companion object {
    var EXTRA_NAME = "extra_name"
    var EXTRA_DESCRIPTION = "extra_description"
    }


    ...

    override fun onActivityCreated(savedInstanceState: Bundle?) {
    super.onActivityCreated(savedInstanceState)

    if (savedInstanceState != null) {
    val descFromBundle = savedInstanceState.getString(EXTRA_DESCRIPTION)
    description = descFromBundle
    }

    if (arguments != null) {
    val categoryName = arguments?.getString(EXTRA_NAME)
    tv_category_name.text = categoryName
    tv_category_description.text = description
    }
    }


    override fun onClick(v: View) {
    when (v.id) {
    R.id.btn_profile -> {

    }

    R.id.btn_show_dialog -> {

    }

    }
    }
    }
    Java
    public class DetailCategoryFragment extends Fragment implements View.OnClickListener {

    ...
    public static String EXTRA_NAME = "extra_name";
    public static String EXTRA_DESCRIPTION = "extra_description";
    private String description;


    public String getDescription() {
    return description;
    }

    public void setDescription(String description) {
    this.description = description;
    }


    ...

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    String categoryName = getArguments().getString(EXTRA_NAME);
    tvCategoryName.setText(categoryName);
    tvCategoryDescription.setText(getDescription());
    }


    @Override
    public void onClick(View v) {
    switch (v.getId()){
    case R.id.btn_profile:
    break;
    case R.id.btn_show_dialog:
    break;

    }
    }
    }
    Kode di atas akan mendemonstrasikan bagaimana melakukan penampilan data yang dikirim melalui perpindahan fragment.
    Sehingga DetailCategoryFragment menjadi seperti ini:
    Kotlin
    class DetailCategoryFragment : Fragment(), View.OnClickListener {

    lateinit var tvCategoryName: TextView
    lateinit var tvCategoryDescription: TextView
    lateinit var btnProfile: Button
    lateinit var btnShowDialog: Button
    var description: String? = null

    companion object {
    var EXTRA_NAME = "extra_name"
    var EXTRA_DESCRIPTION = "extra_description"
    }

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

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    tvCategoryName = view.findViewById(R.id.tv_category_name)
    tvCategoryDescription = view.findViewById(R.id.tv_category_description)
    btnProfile = view.findViewById(R.id.btn_profile)
    btnProfile.setOnClickListener(this)
    btnShowDialog = view.findViewById(R.id.btn_show_dialog)
    btnShowDialog.setOnClickListener(this)
    }

    override fun onActivityCreated(savedInstanceState: Bundle?) {
    super.onActivityCreated(savedInstanceState)

    if (savedInstanceState != null) {
    val descFromBundle = savedInstanceState.getString(EXTRA_DESCRIPTION)
    description = descFromBundle
    }

    if (arguments != null) {
    val categoryName = arguments?.getString(EXTRA_NAME)
    tv_category_name.text = categoryName
    tv_category_description.text = description
    }
    }

    override fun onClick(v: View) {
    when (v.id) {
    R.id.btn_profile -> {

    }

    R.id.btn_show_dialog -> {

    }
    }
    }
    }
    Java
    public class DetailCategoryFragment extends Fragment implements View.OnClickListener {

    TextView tvCategoryName;
    TextView tvCategoryDescription;
    Button btnProfile;
    Button btnShowDialog;
    public static String EXTRA_NAME = "extra_name";
    public static String EXTRA_DESCRIPTION = "extra_description";
    private String description;

    public String getDescription() {
    return description;
    }

    public void setDescription(String description) {
    this.description = description;
    }

    public DetailCategoryFragment() {
    // 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_detail_category, container, false);
    }

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);
    tvCategoryName = view.findViewById(R.id.tv_category_name);
    tvCategoryDescription = view.findViewById(R.id.tv_category_description);
    btnProfile = view.findViewById(R.id.btn_profile);
    btnProfile.setOnClickListener(this);
    btnShowDialog = view.findViewById(R.id.btn_show_dialog);
    btnShowDialog.setOnClickListener(this);
    }

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    String categoryName = getArguments().getString(EXTRA_NAME);
    tvCategoryName.setText(categoryName);
    tvCategoryDescription.setText(getDescription());
    }

    @Override
    public void onClick(View v) {
    switch (v.getId()){
    case R.id.btn_profile:
    break;
    case R.id.btn_show_dialog:
    break;
    }
    }
    }
  4. Baik, tinggal selangkah lagi kita menyelesaikan proses implementasi pengiriman data melalui perpindahan fragment. Sekarang buka kembali CategoryFragment lalu tambahkan baris berikut pada method onClick().
    Kotlin
    override fun onClick(v: View) {
    if (v.id == R.id.btn_detail_category) {
    val mDetailCategoryFragment = DetailCategoryFragment()

    val mBundle = Bundle()
    mBundle.putString(DetailCategoryFragment.EXTRA_NAME, "Lifestyle")
    val description = "Kategori ini akan berisi produk-produk lifestyle"

    mDetailCategoryFragment.arguments = mBundle
    mDetailCategoryFragment.description = description

    val mFragmentManager = fragmentManager
    mFragmentManager?.beginTransaction()?.apply {
    replace(R.id.frame_container, mDetailCategoryFragment, DetailCategoryFragment::class.java.simpleName)
    addToBackStack(null)
    commit()

    }

    }
    }
    Java
    @Override
    public void onClick(View v) {
    if (v.getId() == R.id.btn_detail_category) {
    DetailCategoryFragment mDetailCategoryFragment = new DetailCategoryFragment();

    Bundle mBundle = new Bundle();
    mBundle.putString(DetailCategoryFragment.EXTRA_NAME, "Lifestyle");
    String description = "Kategori ini akan berisi produk-produk lifestyle";

    mDetailCategoryFragment.setArguments(mBundle);
    mDetailCategoryFragment.setDescription(description);

    FragmentManager mFragmentManager = getFragmentManager();
    if (mFragmentManager != null) {
    mFragmentManager
    .beginTransaction()
    .replace(R.id.frame_container, mDetailCategoryFragment, DetailCategoryFragment.class.getSimpleName())
    .addToBackStack(null)
    .commit();
    }

    }
    }
    Sehingga kode CategoryFragment kita sekarang menjadi:
    Kotlin
    class CategoryFragment : Fragment(), View.OnClickListener {


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

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    val btnDetailCategory:Button = view.findViewById(R.id.btn_detail_category)
    btnDetailCategory.setOnClickListener(this)
    }

    override fun onClick(v: View) {
    if (v.id == R.id.btn_detail_category) {
    val mDetailCategoryFragment = DetailCategoryFragment()

    val mBundle = Bundle()
    mBundle.putString(DetailCategoryFragment.EXTRA_NAME, "Lifestyle")
    val description = "Kategori ini akan berisi produk-produk lifestyle"

    mDetailCategoryFragment.arguments = mBundle
    mDetailCategoryFragment.description = description

    val mFragmentManager = fragmentManager
    mFragmentManager?.beginTransaction()?.apply {
    replace(R.id.frame_container, mDetailCategoryFragment, DetailCategoryFragment::class.java.simpleName)
    addToBackStack(null)
    commit()
    }
    }
    }
    }
    Java
    public class CategoryFragment extends Fragment implements View.OnClickListener {

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


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

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);
    Button btnDetailCategory = view.findViewById(R.id.btn_detail_category);
    btnDetailCategory.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
    if (v.getId() == R.id.btn_detail_category) {
    DetailCategoryFragment mDetailCategoryFragment = new DetailCategoryFragment();

    Bundle mBundle = new Bundle();
    mBundle.putString(DetailCategoryFragment.EXTRA_NAME, "Lifestyle");
    String description = "Kategori ini akan berisi produk-produk lifestyle";

    mDetailCategoryFragment.setArguments(mBundle);
    mDetailCategoryFragment.setDescription(description);

    /*
    Method addToBackStack akan menambahkan fragment ke backstack
    Behaviour dari back button akan cek fragment dari backstack,
    jika ada fragment di dalam backstack maka fragment yang akan di close / remove
    jika sudah tidak ada fragment di dalam backstack maka activity yang akan di close / finish
    */
    FragmentManager mFragmentManager = getFragmentManager();
    if (mFragmentManager != null) {
    mFragmentManager
    .beginTransaction()
    .replace(R.id.frame_container, mDetailCategoryFragment, DetailCategoryFragment.class.getSimpleName())
    .addToBackStack(null)
    .commit();
    }
    }
    }
    }
  5. Sekarang setelah selesai semua, silakan jalankan aplikasinya untuk melihat hasil kode yang di atas. Seharusnya ketika Anda mengklik tombol Kategory Lifestyle pada CategoryFragment akan ada data yang dikirimkan sewaktu perpindahan fragment itu melalui object bundle dan mekanisme metode setter and getter. Tampilan aplikasi Anda sekarang sudah menjadi seperti ini.
    20181113141828b6dd98d3ea44fe06dfe5d6563fec30fc.gif

Bedah Kode

Mantap! Anda sekarang sudah bisa berpindah dari satu fragment ke fragment lain dengan mengirimkan data. Ya seperti yang sudah dijelaskan di awal, ada dua mekanisme mengirimkan data antar fragment yaitu:
  • Dengan Menggunakan Bundle

    Kotlin
    val mBundle = Bundle()
    mBundle.putString(DetailCategoryFragment.EXTRA_NAME, "Lifestyle")
    Java
    Bundle mBundle = new Bundle();
    mBundle.putString(DetailCategoryFragment.EXTRA_NAME, "Lifestyle");
    Pada kode di atas kita menggunakan obyek bundle untuk mengirimkan data antar fragment. Perhatikan cara yang digunakan sama dengan cara yang telah kita implementasikan sebelumnya di activity. Setelah dibuat obyeknya dan data yang mau dikirimkan apa, kita hanya perlu menambahkan sebaris kode berikut:
    Kotlin
    mDetailCategoryFragment.arguments = mBundle
    Java
    mDetailCategoryFragment.setArguments(mBundle);
    Cara mengambil data yang dikirimkan melalui obyek bundle pada fragment tujuan pun, sangatlah mudah. Cukup memanggil metode getArguments() di fragment DetailCategoryFragment seperti berikut:
    Kotlin
    val categoryName = arguments?.getString(EXTRA_NAME)
    Java
    String categoryName = getArguments().getString(EXTRA_NAME);
    Kelas Bundle merupakan kelas map data string untuk obyek-obyek parcelable. Di sini kita bisa menginput lebih dari satu parameter/variabel ke dalam obyek Bundle.


  • Dengan Menggunakan Setter dan Getter

    Kelas fragment adalah kelas java pada umumnya, dengan menggunakan metode setter and getter untuk mengirimkan parameter/variabel dari satu fragment ke fragment lainnya. Seperti baris berikut:
    Kotlin
    val description = "Kategori ini akan berisi produk-produk lifestyle"
    mDetailCategoryFragment.description = description
    Java
    String description = "Kategori ini akan berisi produk-produk lifestyle";
    mDetailCategoryFragment.setDescription(description);
    Yang mana isi kode pada kelas DetailCategoryFragment sebagai berikut:
    Kotlin
    var description: String? = null

    companion object {
    var EXTRA_NAME = "extra_name"
    var EXTRA_DESCRIPTION = "extra_description"
    }
    Java
    public static String EXTRA_NAME = "extra_name";
    private String description;
    public DetailCategoryFragment() {
    // Required empty public constructor
    }
    public String getDescription() {
    return description;
    }
    public void setDescription(String description) {
    this.description = description;
    }
    Cara menggunakannya juga cukup mudah, yakni hanya dengan menempatkan value yang ingin dikirimkan via metode setter lalu diambil dengan menggunakan metode getter seperti pada baris berikut:
    Kotlin
    tv_category_description.text = description
    Java
    tvCategoryDescription.setText(getDescription());


Mudah kan? Dua cara di atas sudah umum dilakukan dan diimplementasikan ke dalam project pengembangan aplikasi android. Selamat! Sejauh ini Anda sudah memahami lebih jauh tentang fragment, mulai dari bagaimana membuat, menempelkan ke activity, hingga berpindah antar fragment dengan atau tanpa membawa data.

Codelab Fragment untuk Dialog

Selanjutnya kita masuk ke pemanfaatan fragment untuk menampilkan sebuah custom dialog. Kita akan menggunakan DialogFragment lengkap dengan interaksinya. Siap? Ayo kita lanjutkan ngoding-nya.
  1. Buat kembali satu kelas fragment dengan nama OptionDialogFragment . Jangan lupa uncheck pilihan include fragment factory methods dan include interface methods.
    201811131424436d8d68265cb58fa44814947a409d0855
  2. Ketika sudah diciptakan, pada fragment_option_dialog.xml kondisikan kodenya menjadi seperti berikut:
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    android:padding="16dp">

    <TextView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginBottom="16dp"
    android:text="@string/question_coach" />

    <RadioGroup
    android:id="@+id/rg_options"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical">

    <RadioButton
    android:id="@+id/rb_saf"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginBottom="16dp"
    android:text="@string/sir_alex_ferguson" />

    <RadioButton
    android:id="@+id/rb_mou"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginBottom="16dp"
    android:text="@string/jose_mourinho" />

    <RadioButton
    android:id="@+id/rb_lvg"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginBottom="16dp"
    android:text="@string/louis_van_gaal" />

    <RadioButton
    android:id="@+id/rb_moyes"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginBottom="16dp"
    android:text="@string/david_moyes" />
    </RadioGroup>

    <LinearLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal">

    <Button
    android:id="@+id/btn_close"
    style="?android:attr/buttonBarButtonStyle"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginEnd="4dp"
    android:layout_marginRight="4dp"
    android:layout_weight="0.5"
    android:text="@string/close" />

    <Button
    android:id="@+id/btn_choose"
    style="?android:attr/buttonBarButtonStyle"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginLeft="4dp"
    android:layout_marginStart="4dp"
    android:layout_weight="0.5"
    android:text="@string/choose" />
    </LinearLayout>

    </LinearLayout>
  3. Setelah selesai dengan berkas layout xml, lanjutkan ngoding untuk OptionDialogFragment. Pertama, kenalkan obyek yang ada di dalam layout seperti ini:
    Kotlin
        private lateinit var btnChoose: Button
    private lateinit var btnClose: Button
    private lateinit var rgOptions: RadioGroup
    private lateinit var rbSaf: RadioButton
    private lateinit var rbMou: RadioButton
    private lateinit var rbLvg: RadioButton
    private lateinit var rbMoyes: RadioButton
    private var optionDialogListener: OnOptionDialogListener? = null


    ...

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    btnChoose = view.findViewById(R.id.btn_choose)
    btnChoose.setOnClickListener(this)
    btnClose = view.findViewById(R.id.btn_close)
    btnClose.setOnClickListener(this)
    rgOptions = view.findViewById(R.id.rg_options)
    rbSaf = view.findViewById(R.id.rb_saf)
    rbLvg = view.findViewById(R.id.rb_lvg)
    rbMou = view.findViewById(R.id.rb_mou)
    rbMoyes = view.findViewById(R.id.rb_moyes)
    }
    Java
       Button btnChoose, btnClose;
    RadioGroup rgOptions;
    RadioButton rbSaf, rbMou, rbLvg, rbMoyes;
    OnOptionDialogListener optionDialogListener;


    ...

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);
    btnChoose = view.findViewById(R.id.btn_choose);
    btnChoose.setOnClickListener(this);
    btnClose = view.findViewById(R.id.btn_close);
    btnClose.setOnClickListener(this);
    rgOptions = view.findViewById(R.id.rg_options);
    rbSaf = view.findViewById(R.id.rb_saf);
    rbLvg = view.findViewById(R.id.rb_lvg);
    rbMou = view.findViewById(R.id.rb_mou);
    rbMoyes = view.findViewById(R.id.rb_moyes);
    }
    Selanjutnya kita beri aksi untuk button-nya dan beri pula kelas interface dan ubah turunannya dari Fragment menjadi DialogFragment.
    Kotlin
    class OptionDialogFragment : DialogFragment(), View.OnClickListener {

    ...

    override fun onClick(v: View) {
    when (v.id) {
    R.id.btn_close -> dialog?.cancel()

    R.id.btn_choose -> {
    val checkedRadioButtonId = rg_options.checkedRadioButtonId
    if (checkedRadioButtonId != -1) {
    var coach: String? = null
    when (checkedRadioButtonId) {
    R.id.rb_saf -> coach = rbSaf.text.toString().trim()

    R.id.rb_mou -> coach = rbMou.text.toString().trim()

    R.id.rb_lvg -> coach = rbLvg.text.toString().trim()

    R.id.rb_moyes -> coach = rbMoyes.text.toString().trim()
    }

    if (optionDialogListener != null) {
    optionDialogListener?.onOptionChosen(coach)
    }
    dialog?.dismiss()
    }
    }
    }
    }

    interface OnOptionDialogListener {
    fun onOptionChosen(text: String?)
    }

    }
    Java
    public class OptionDialogFragment extends DialogFragment implements View.OnClickListener {

    ...

    @Override
    public void onClick(View v) {
    switch (v.getId()) {
    case R.id.btn_close:
    getDialog().cancel();
    break;

    case R.id.btn_choose:
    int checkedRadioButtonId = rgOptions.getCheckedRadioButtonId();
    if (checkedRadioButtonId != -1) {
    String coach = null;
    switch (checkedRadioButtonId) {
    case R.id.rb_saf:
    coach = rbSaf.getText().toString().trim();
    break;

    case R.id.rb_mou:
    coach = rbMou.getText().toString().trim();
    break;

    case R.id.rb_lvg:
    coach = rbLvg.getText().toString().trim();
    break;

    case R.id.rb_moyes:
    coach = rbMoyes.getText().toString().trim();
    break;
    }

    if (optionDialogListener != null) {
    optionDialogListener.onOptionChosen(coach);
    }
    getDialog().dismiss();
    }
    break;
    }
    }


    public interface OnOptionDialogListener {
    void onOptionChosen(String text);
    }

    }
    Kemudian tambahkan kode berikut untuk mengelola optionDialogListener ketika fragment dipanggil dan dimatikan:
    Kotlin
    override fun onAttach(context: Context) {
    super.onAttach(context)

    val fragment = parentFragment

    if (fragment is DetailCategoryFragment) {
    val detailCategoryFragment = fragment
    this.optionDialogListener = detailCategoryFragment.optionDialogListener
    }
    }

    override fun onDetach() {
    super.onDetach()
    this.optionDialogListener = null
    }
    Java
    @Override
    public void onAttach(Context context) {
    super.onAttach(context);

    Fragment fragment = getParentFragment();

    if (fragment instanceof DetailCategoryFragment) {
    DetailCategoryFragment detailCategoryFragment = (DetailCategoryFragment) fragment;
    this.optionDialogListener = detailCategoryFragment.optionDialogListener;
    }
    }

    @Override
    public void onDetach() {
    super.onDetach();
    this.optionDialogListener = null;
    }
    Kondisikan kodenya menjadi sebagai berikut:
    Kotlin
    class OptionDialogFragment : DialogFragment(), View.OnClickListener {

    private lateinit var btnChoose: Button
    private lateinit var btnClose: Button
    private lateinit var rgOptions: RadioGroup
    private lateinit var rbSaf: RadioButton
    private lateinit var rbMou: RadioButton
    private lateinit var rbLvg: RadioButton
    private lateinit var rbMoyes: RadioButton
    private var optionDialogListener: OnOptionDialogListener? = null

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
    savedInstanceState: Bundle?): View? {
    return inflater.inflate(R.layout.fragment_option_dialog, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    btnChoose = view.findViewById(R.id.btn_choose)
    btnChoose.setOnClickListener(this)
    btnClose = view.findViewById(R.id.btn_close)
    btnClose.setOnClickListener(this)
    rgOptions = view.findViewById(R.id.rg_options)
    rbSaf = view.findViewById(R.id.rb_saf)
    rbLvg = view.findViewById(R.id.rb_lvg)
    rbMou = view.findViewById(R.id.rb_mou)
    rbMoyes = view.findViewById(R.id.rb_moyes)
    }

    override fun onAttach(context: Context) {
    super.onAttach(context)

    val fragment = parentFragment

    if (fragment is DetailCategoryFragment) {
    val detailCategoryFragment = fragment
    this.optionDialogListener = detailCategoryFragment.optionDialogListener
    }
    }

    override fun onDetach() {
    super.onDetach()
    this.optionDialogListener = null
    }

    override fun onClick(v: View) {
    when (v.id) {
    R.id.btn_close -> dialog?.cancel()

    R.id.btn_choose -> {
    val checkedRadioButtonId = rg_options.checkedRadioButtonId
    if (checkedRadioButtonId != -1) {
    var coach: String? = null
    when (checkedRadioButtonId) {
    R.id.rb_saf -> coach = rbSaf.text.toString().trim()

    R.id.rb_mou -> coach = rbMou.text.toString().trim()

    R.id.rb_lvg -> coach = rbLvg.text.toString().trim()

    R.id.rb_moyes -> coach = rbMoyes.text.toString().trim()
    }

    if (optionDialogListener != null) {
    optionDialogListener?.onOptionChosen(coach)
    }
    dialog?.dismiss()
    }
    }
    }
    }

    interface OnOptionDialogListener {
    fun onOptionChosen(text: String?)
    }
    }
    Java
    public class OptionDialogFragment extends DialogFragment implements View.OnClickListener {

    Button btnChoose, btnClose;
    RadioGroup rgOptions;
    RadioButton rbSaf, rbMou, rbLvg, rbMoyes;
    OnOptionDialogListener optionDialogListener;

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

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

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);
    btnChoose = view.findViewById(R.id.btn_choose);
    btnChoose.setOnClickListener(this);
    btnClose = view.findViewById(R.id.btn_close);
    btnClose.setOnClickListener(this);
    rgOptions = view.findViewById(R.id.rg_options);
    rbSaf = view.findViewById(R.id.rb_saf);
    rbLvg = view.findViewById(R.id.rb_lvg);
    rbMou = view.findViewById(R.id.rb_mou);
    rbMoyes = view.findViewById(R.id.rb_moyes);
    }

    @Override
    public void onAttach(Context context) {
    super.onAttach(context);
    Fragment fragment = getParentFragment();

    if (fragment instanceof DetailCategoryFragment) {
    DetailCategoryFragment detailCategoryFragment = (DetailCategoryFragment) fragment;
    this.optionDialogListener = detailCategoryFragment.optionDialogListener;
    }
    }

    @Override
    public void onDetach() {
    super.onDetach();
    this.optionDialogListener = null;
    }

    @Override
    public void onClick(View v) {
    switch (v.getId()) {
    case R.id.btn_close:
    getDialog().cancel();
    break;

    case R.id.btn_choose:
    int checkedRadioButtonId = rgOptions.getCheckedRadioButtonId();
    if (checkedRadioButtonId != -1) {
    String coach = null;
    switch (checkedRadioButtonId) {
    case R.id.rb_saf:
    coach = rbSaf.getText().toString().trim();
    break;

    case R.id.rb_mou:
    coach = rbMou.getText().toString().trim();
    break;

    case R.id.rb_lvg:
    coach = rbLvg.getText().toString().trim();
    break;

    case R.id.rb_moyes:
    coach = rbMoyes.getText().toString().trim();
    break;
    }

    if (optionDialogListener != null) {
    optionDialogListener.onOptionChosen(coach);
    }
    getDialog().dismiss();
    }
    break;
    }
    }

    public interface OnOptionDialogListener {
    void onOptionChosen(String text);
    }
    }
    Ingat, jangan lupa untuk menambahkan inherit ke kelas DialogFragment.
  4. Tambahkan beberapa baris pada metode onClick() di DetailCategoryFragment menjadi sebagai berikut:
    Kotlin
    override fun onClick(v: View) {
    when (v.id) {
    R.id.btn_profile -> {

    }

    R.id.btn_show_dialog -> {
    val mOptionDialogFragment = OptionDialogFragment()

    val mFragmentManager = childFragmentManager
    mOptionDialogFragment.show(mFragmentManager, OptionDialogFragment::class.java.simpleName)

    }
    }
    }
    Java
    @Override
    public void onClick(View v) {
    switch (v.getId()) {
    case R.id.btn_profile:
    break;

    case R.id.btn_show_dialog:
    OptionDialogFragment mOptionDialogFragment = new OptionDialogFragment();

    FragmentManager mFragmentManager = getChildFragmentManager();
    mOptionDialogFragment.show(mFragmentManager, OptionDialogFragment.class.getSimpleName());
    break;

    }
    }
  5. Kemudian tambahkan OptionDialogFragment pada DetailCategoryFragment seperti berikut:
    Kotlin
    class DetailCategoryFragment : Fragment(), View.OnClickListener {

    ...

    internal var optionDialogListener: OptionDialogFragment.OnOptionDialogListener = object : OptionDialogFragment.OnOptionDialogListener {
    override fun onOptionChosen(text: String?) {
    Toast.makeText(activity, text, Toast.LENGTH_SHORT).show()
    }
    }

    }
    Java
    public class DetailCategoryFragment extends Fragment implements View.OnClickListener {

    ...

    OptionDialogFragment.OnOptionDialogListener optionDialogListener = new OptionDialogFragment.OnOptionDialogListener() {
    @Override
    public void onOptionChosen(String text) {
    Toast.makeText(getActivity(), text, Toast.LENGTH_SHORT).show();
    }
    };

    }
  6. Sehingga kode DetailCategoryFragment kita saat ini menjadi seperti ini:
    Kotlin
    class DetailCategoryFragment : Fragment(), View.OnClickListener {

    lateinit var tvCategoryName: TextView
    lateinit var tvCategoryDescription: TextView
    lateinit var btnProfile: Button
    lateinit var btnShowDialog: Button
    var description: String? = null

    companion object {
    var EXTRA_NAME = "extra_name"
    var EXTRA_DESCRIPTION = "extra_description"
    }

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

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    tvCategoryName = view.findViewById(R.id.tv_category_name);
    tvCategoryDescription = view.findViewById(R.id.tv_category_description);
    btnProfile = view.findViewById(R.id.btn_profile);
    btnProfile.setOnClickListener(this);
    btnShowDialog = view.findViewById(R.id.btn_show_dialog);
    btnShowDialog.setOnClickListener(this);
    }

    override fun onActivityCreated(savedInstanceState: Bundle?) {
    super.onActivityCreated(savedInstanceState)

    if (savedInstanceState != null) {
    val descFromBundle = savedInstanceState.getString(EXTRA_DESCRIPTION)
    description = descFromBundle
    }

    if (arguments != null) {
    val categoryName = arguments?.getString(EXTRA_NAME)
    tv_category_name.text = categoryName
    tv_category_description.text = description
    }
    }


    override fun onSaveInstanceState(outState: Bundle) {
    super.onSaveInstanceState(outState)

    outState.putString(EXTRA_DESCRIPTION, description)
    }

    override fun onClick(v: View) {
    when (v.id) {
    R.id.btn_profile -> {
    val mIntent = Intent(activity, ProfileActivity::class.java)
    startActivity(mIntent)
    }

    R.id.btn_show_dialog -> {
    val mOptionDialogFragment = OptionDialogFragment()

    val mFragmentManager = childFragmentManager
    mOptionDialogFragment.show(mFragmentManager, OptionDialogFragment::class.java.simpleName)
    }
    }
    }

    internal var optionDialogListener: OptionDialogFragment.OnOptionDialogListener = object : OptionDialogFragment.OnOptionDialogListener {
    override fun onOptionChosen(text: String?) {
    Toast.makeText(activity, text, Toast.LENGTH_SHORT).show()
    }
    }
    }
    Java
    public class DetailCategoryFragment extends Fragment implements View.OnClickListener {

    TextView tvCategoryName;
    TextView tvCategoryDescription;
    Button btnProfile;
    Button btnShowDialog;
    public static String EXTRA_NAME = "extra_name";
    public static String EXTRA_DESCRIPTION = "extra_description";
    private String description;

    public String getDescription() {
    return description;
    }

    public void setDescription(String description) {
    this.description = description;
    }

    public DetailCategoryFragment() {
    // 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_detail_category, container, false);
    }

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);
    tvCategoryName = view.findViewById(R.id.tv_category_name);
    tvCategoryDescription = view.findViewById(R.id.tv_category_description);
    btnProfile = view.findViewById(R.id.btn_profile);
    btnProfile.setOnClickListener(this);
    btnShowDialog = view.findViewById(R.id.btn_show_dialog);
    btnShowDialog.setOnClickListener(this);
    }

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    if (savedInstanceState != null) {
    String descFromBundle = savedInstanceState.getString(EXTRA_DESCRIPTION);
    setDescription(descFromBundle);
    }

    if (getArguments() != null) {
    String categoryName = getArguments().getString(EXTRA_NAME);
    tvCategoryName.setText(categoryName);
    tvCategoryDescription.setText(getDescription());

    }
    }

    @Override
    public void onSaveInstanceState(@NonNull Bundle outState) {
    super.onSaveInstanceState(outState);

    outState.putString(EXTRA_DESCRIPTION, getDescription());
    }

    @Override
    public void onClick(View v) {
    switch (v.getId()){
    case R.id.btn_profile:
    break;
    case R.id.btn_show_dialog:
    OptionDialogFragment mOptionDialogFragment = new OptionDialogFragment();

    FragmentManager mFragmentManager = getChildFragmentManager();
    mOptionDialogFragment.show(mFragmentManager, OptionDialogFragment.class.getSimpleName());
    break;
    }
    }
    OptionDialogFragment.OnOptionDialogListener optionDialogListener = new OptionDialogFragment.OnOptionDialogListener() {
    @Override
    public void onOptionChosen(String text) {
    Toast.makeText(getActivity(), text, Toast.LENGTH_SHORT).show();
    }
    };
    }
  7. Sekarang jalankan kembali aplikasi dan klik pada tombol Tampilkan sebuah dialog. Hasilnya akan muncul obyek OptionDialogFragment yang baru saja dibuat. Coba Anda pilih salah satu option yang ada dan klik tombol Pilih. Lihat, hasil dari yang kita pilih, tampil dalam bentuk soft message (Toast).
    20181113145020cd1fa020635347db3bdf987ff0096c94.gif

Bedah Kode

DialogFragment

Kotlin
class OptionDialogFragment : DialogFragment()
Java
public class OptionDialogFragment extends DialogFragment
Sama dengan obyek fragment seperti sebelumnya, di sini kelas fragment yang kita buat inherit ke DialogFragment. Dengan begitu obyek fragment sekarang merupakan obyek dialog yang akan tampil mengambang di layar. Seperti pada obyek modal pada platform lain, obyek DialogFragment dapat disesuaikan tampilan dan fungsinya secara spesifik. Di sini kita menampillkan sebuah dialog ke pengguna untuk memilih sebuah opsi yang tersedia
Kotlin
internal var optionDialogListener: OptionDialogFragment.OnOptionDialogListener = object : OptionDialogFragment.OnOptionDialogListener {
override fun onOptionChosen(text: String?) {
Toast.makeText(activity, text, Toast.LENGTH_SHORT).show()
}
}
Java
public final OptionDialogFragment.OnOptionDialogListener optionDialogListener = new OptionDialogFragment.OnOptionDialogListener() {
@Override
public void onOptionChosen(String text) {
Toast.makeText(getActivity(), text, Toast.LENGTH_SHORT).show();
}
};

OptionDialogFragment mOptionDialogFragment = new OptionDialogFragment();
mOptionDialogFragment.setOnOptionDialogListener(new OptionDialogFragment.OnOptionDialogListener() {
@Override
public void onOptionChosen(String text) {
Toast.makeText(getActivity(), text, Toast.LENGTH_SHORT).show();
}
});

Kotlin
val mFragmentManager = childFragmentManager
mOptionDialogFragment.show(mFragmentManager, OptionDialogFragment::class.java.simpleName)
Java
FragmentManager mFragmentManager = getChildFragmentManager();
mOptionDialogFragment.show(mFragmentManager, OptionDialogFragment.class.getSimpleName());
Proses pemanggilannya pun hampir sama dengan yang kita lakukan sebelumnya. Namun perbedaanya ada di sini:
Kotlin
val mFragmentManager = childFragmentManager
Java
FragmentManager mFragmentManager = getChildFragmentManager();
Kita tidak menggunakan getFragmentManager() saat ini. Alih- alih, getChildFragmentManager() merupakan pilihan yang tepat untuk kondisi saat ini, yakni sebuah nested fragment / fragment bersarang. Karena OptionDialogFragment dipanggil di dalam sebuah obyek fragment yang telah ada sebelumnya yaitu DetailDialogFragment, maka demi performa lebih baik gunakanlah getChildFragmentManager() sebagai pilihan yang lebih tepat.
Kotlin
mOptionDialogFragment.show(mFragmentManager, OptionDialogFragment::class.java.simpleName)
Java
mOptionDialogFragment.show(mFragmentManager, OptionDialogFragment.class.getSimpleName());
Baris di atas digunakan untuk menampilkan obyek OptionDialogFragment ke layar.
Untuk proses handling event ketika tombol Pilih pada OptionDialogFragment diklik, kita menggunakan implementasi interface. Bagi yang belum paham tentang definisi interface pada pemrograman Java, inilah definisi singkatnya.
Interface adalah sebuah kelas yang terdiri kumpulan method yang hanya memuat deklarasi dan struktur method, tanpa detail implementasinya.
Tautan berikut akan membantu pemahaman Anda tentang Interface.

Kelas interface yang kita punya pada OptionDialogFragment adalah sebagai berikut:
Kotlin
interface OnOptionDialogListener {
fun onOptionChosen(text: String?)
}
Java
public interface OnOptionDialogListener{
void onOptionChosen(String text);
}
Di mana kita menggunakannya pada bagian ini:
Kotlin
val checkedRadioButtonId = rgOptions.checkedRadioButtonId
if (checkedRadioButtonId != -1) {
var coach: String? = null
when (checkedRadioButtonId) {
R.id.rb_saf -> coach = rbSaf.text.toString().trim()

R.id.rb_mou -> coach = rbMou.text.toString().trim()

R.id.rb_lvg -> coach = rbLvg.text.toString().trim()

R.id.rb_moyes -> coach = rbMoyes.text.toString().trim()
}
optionDialogListener?.onOptionChosen(coach)
dialog?.dismiss()
}
Java
int checkedRadioButtonId = rgOptions.getCheckedRadioButtonId();
if (checkedRadioButtonId != -1){
String coach = null;
switch (checkedRadioButtonId){
case R.id.rb_saf:
coach = rbSaf.getText().toString().trim();
break;
case R.id.rb_mou:
coach = rbMou.getText().toString().trim();
break;
case R.id.rb_lvg:
coach = rbLvg.getText().toString().trim();
break;
case R.id.rb_moyes:
coach = rbMoyes.getText().toString().trim();
break;
}
getOnOptionDialogListener().onOptionChosen(coach);
getDialog().cancel();
}
Dan mengimplementasikannya pada bagian ini:
Kotlin
internal var optionDialogListener: OptionDialogFragment.OnOptionDialogListener = object : OptionDialogFragment.OnOptionDialogListener {
override fun onOptionChosen(text: String?) {
Toast.makeText(activity, text, Toast.LENGTH_SHORT).show()
}
}
Java
mOptionDialogFragment.setOnOptionDialogListener(new OptionDialogFragment.OnOptionDialogListener() {
@Override
public void onOptionChosen(String text) {
Toast.makeText(getActivity(), text, Toast.LENGTH_SHORT).show();
}
});
Jadi ketika pengguna menekan tombol Pilih setelah memilih salah satu pilihan, baris ini akan dieksekusi:
Kotlin
optionDialogListener?.onOptionChosen(coach)
Java
getOnOptionDialogListener().onOptionChosen(coach);
Kemudian metode onOptionChosen() pada baris ini akan dipanggil untuk menampilkan nilai dari pilihan yang dipilih pada toast.
Kotlin
internal var optionDialogListener: OptionDialogFragment.OnOptionDialogListener = object : OptionDialogFragment.OnOptionDialogListener {
override fun onOptionChosen(text: String?) {
Toast.makeText(activity, text, Toast.LENGTH_SHORT).show()
}
}
Java
mOptionDialogFragment.setOnOptionDialogListener(new OptionDialogFragment.OnOptionDialogListener() {
@Override
public void onOptionChosen(String text) {
Toast.makeText(getActivity(), text, Toast.LENGTH_SHORT).show();
}
});
Cool, sejauh ini kami harap Anda sudah lebih memahami tentang bentuk dan implementasi fragment seperti apa.

Codelab Memanggil Activity dari fragment

Terakhir sebelum sesi ini selesai kita akan belajar bagaimana menjalankan sebuah activity dari fragment. Caranya pun hampir sama dengan apa yang telah kita pelajari di activity.
  1. Buat sebuah activity dengan nama ProfileActivity.
    2019030411002258d41ca56db629e5fec7babacdc4dc48
  2. Setelah terbentuk, pada activity_profile.xml kondisikan kode yang ada menjadi sebagai berikut:
    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="16dp">

    <TextView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="@string/this_profile" />

    </RelativeLayout>
  3. Kemudian pada method onClick() pilihan R.id.btn_profile di kelas DetailCategoryFragment , tambahkan beberapa baris berikut untuk menjalankan ProfileActivity:
    Kotlin
    override fun onClick(v: View) {
    when (v.id) {
    R.id.btn_profile -> {
    val mIntent = Intent(activity, ProfileActivity::class.java)
    startActivity(mIntent)

    }

    R.id.btn_show_dialog -> {
    val mOptionDialogFragment = OptionDialogFragment()

    val mFragmentManager = childFragmentManager
    mOptionDialogFragment.show(mFragmentManager, OptionDialogFragment::class.java.simpleName)
    }
    }
    }
    Java
    @Override
    public void onClick(View v) {
    switch (v.getId()) {
    case R.id.btn_profile:
    Intent mIntent = new Intent(getActivity(), ProfileActivity.class);
    startActivity(mIntent);

    break;

    case R.id.btn_show_dialog:
    OptionDialogFragment mOptionDialogFragment = new OptionDialogFragment();

    FragmentManager mFragmentManager = getChildFragmentManager();
    mOptionDialogFragment.show(mFragmentManager, OptionDialogFragment.class.getSimpleName());
    break;
    }
    }
  4. Setelah selesai semua, coba jalankan aplikasi Anda dan klik tombol Ke Halaman Profile Activity.
    20181113154227a573df47147875a68ba406b61314d6e4.gif

Bedah Kode

Perbedaan Activity dan Fragment

Lihat kode di bawah ini:
Kotlin
val mIntent = Intent(activity, ProfileActivity::class.java)
startActivity(mIntent)
Java
Intent mIntent = new Intent(getActivity(), ProfileActivity.class);
startActivity(mIntent);
Mengapa pakai activity/getActivity() ? Padahal pada modul tentang Intent sebelumnya, kita menggunakan this@MainActivity/MainActivity.this sebagai context. Hal ini karena kita menggunakan Fragment, sedangkan fungsi this hanya bisa dipanggil melalui Activity. Oleh karena itulah, kita menggunakan activity/getActivity() untuk mendapatkan context dari activity tempat fragment ini menempel.
Perbedaan yang lainnya yaitu dalam pemanggilan id, contohnya di bawah ini:
Kotlin
class DetailCategoryFragment : Fragment(){
...
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
tvCategoryName = view.findViewById(R.id.tv_category_name)
...
}
}
Java
    @Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
tvCategoryName = view.findViewById(R.id.tv_category_name);
}
Jika pada activity anda dapat langsung memanggil findViewById tanpa kode view. di depannya, namun pada fragment anda harus membutuhkan kode tersebut, mengapa demikian? Hal ini karena pada fragment dalam inisialisasi layout kita menggunaan inflater yang kemudian pada method onCreateView masuk pada variabel view.

Jadi, itulah dua perbedaan antara fragment dan activity dari segi kode yaitu:
  1. Context menggunakan getActivity/activity, bukan this@MainActivity/MainActivity.this
  2. Pemanggilan id menggunakan view.findFiewById, bukan findFiewById
Anda telah melalui banyak perjuangan dan menulis banyak barisan kode. Anda sudah berhasil memahami bagaimana komponen fragment bekerja. Selanjutnya, akan ada codelab tentang implementasi lanjut dari fragment. Ayo kita lanjut lagi, Semangat ngoding!

Untuk source code bagian ini bisa Anda unduh di

LihatTutupKomentar

Tutorial Cara Deployment (Build APK &amp; 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...