loading...

Navigation Component: How to pass data between fragments using Bundle and ViewModel

kulloveth profile image Loveth Nwokike Updated on ・15 min read

Navigation Component is one of the Android jetpacks library. Its purpose is to have only one activity that serves as an entry point to your application using fragment for all UI screen. It comes with a lot of benefits which includes handling fragment transaction,control back stack by default, viewmodel support which I'm going to be explaining in this post

This post demonstrates how to pass data between two fragments using both viewmodel and Bundle.

Bundle is used to pass data between both activities and fragments, it maps values to String keys and then uses the key to retrieve the value.

Viewmodel is a helper class designed to manage UI related data in a life-cycle conscious way.It is responsible for preparing data for the UI and therefore helps to separate the view from business logics. Fragments can share a viewmodel using their activity scope to handle communication between each other.

The project sample fetches data from https://restcountries.eu/rest/v2/all which displays all countries on the screen, when the user clicks on each country it opens up the detail fragment showing some information about the country.This project is in kotlin and the final ui is shown below
Countries
Translation
Languages
To use Navigation component we need to add its dependency to build.gradle, I will also be listing other dependencies used in the project. First apply kotlin kapt at the top of the gradle file apply plugin: 'kotlin-kapt' then add the following dependencies

Dependencies

    implementation 'com.github.ar-android:AndroidSvgLoader:1.0.2'
   //navigation component
    implementation 'androidx.navigation:navigation-fragment-ktx:2.2.1'
    implementation 'androidx.navigation:navigation-ui-ktx:2.2.1'
   //viewpager2
    implementation "androidx.viewpager2:viewpager2:1.0.0"
    implementation 'com.google.android.material:material:1.1.0'
    implementation 'androidx.recyclerview:recyclerview:1.1.0'
    implementation 'androidx.cardview:cardview:1.0.0'
    //retrofit
    implementation 'com.squareup.retrofit2:converter-gson:2.7.1'
    implementation 'com.google.code.gson:gson:2.8.6'
    implementation 'com.squareup.retrofit2:retrofit:2.7.1'
    //rxjava2
    implementation 'io.reactivex.rxjava2:rxjava:2.2.13'
    implementation 'io.reactivex.rxjava2:rxandroid:2.0.2'
    implementation 'com.squareup.retrofit2:adapter-rxjava2:2.4.0'
    def lifecycle_version = "2.2.0"
    implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
    kapt "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"

RetrofitService.kt

package kulloveth.developer.com.countrydetails.api
import io.reactivex.Single
import kulloveth.developer.com.countrydetails.data.model.CountryDetails
import retrofit2.Response
import retrofit2.Retrofit
import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory
import retrofit2.converter.gson.GsonConverterFactory
import retrofit2.http.GET
interface RetrofitService {
@GET("rest/v2/all")
fun fetchCharacterName(): Single<Response<List<CountryDetails>>>
companion object {
fun getRetrofitInstance(): RetrofitService {
val retrofit: Retrofit = Retrofit.Builder()
.baseUrl("https://restcountries.eu/")
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build()
return retrofit.create(RetrofitService::class.java)}}
}

The above class is using the retrofit @Get annotation to pull the data from the Url

CountryDetails.kt

package kulloveth.developer.com.countrydetails.data.model
import com.google.gson.annotations.Expose
import com.google.gson.annotations.SerializedName
import java.io.Serializable
data class CountryDetails(
    val id: Int,
    @SerializedName("name")
    @Expose
    val name: String,
    @SerializedName("flag")
    @Expose
    val flag: String,
    val timezones : ArrayList<String>,
    @SerializedName("translations")
    val translations: Translations,
    @SerializedName("languages")
    val language: List<Language>
) : Serializable

Language.kt

package kulloveth.developer.com.countrydetails.data.model
import com.google.gson.annotations.Expose
import com.google.gson.annotations.SerializedName
import java.io.Serializable
data class CountryDetails(
    val id: Int,
    @SerializedName("name")
    @Expose
    val name: String,
    @SerializedName("flag")
    @Expose
    val flag: String,
    val timezones : ArrayList<String>,
    @SerializedName("translations")
    val translations: Translations,
    @SerializedName("languages")
    val language: List<Language>
) : Serializable

Translations.kt

package kulloveth.developer.com.countrydetails.data.model
import java.io.Serializable
data class Translations(
    val de: String,
    val es: String,
    val fr: String,
    val ja: String,
    val it: String,
    val br: String,
    val pt: String,
    val nl: String,
    val hr: String,
    val fa: String
) : Serializable

The above classes are the data class for representing the data structure present in the api also called model class

CountryDetailsRepository.kt

package kulloveth.developer.com.countrydetails.data
import android.util.Log
import androidx.lifecycle.MutableLiveData
import io.reactivex.SingleObserver
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers
import kulloveth.developer.com.countrydetails.api.RetrofitService
import kulloveth.developer.com.countrydetails.data.model.CountryDetails
import retrofit2.Response
class CountryDetailsRepository {
val countrysLiveData: MutableLiveData<List<CountryDetails>> by lazy {
        MutableLiveData<List<CountryDetails>>().also {
            fetchCountryDetails()
        }
    }
fun fetchCountryDetails() {
        val api = RetrofitService.getRetrofitInstance().fetchCharacterName()
        api.subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(object : SingleObserver<Response<List<CountryDetails>>> {
                override fun onSuccess(t: Response<List<CountryDetails>>) {
                    countrysLiveData.value = t.body()
                    Log.d("rest", "" + countrysLiveData)
                }
override fun onSubscribe(d: Disposable) {}
override fun onError(e: Throwable) {}})
    }}

CountrysViewModel.kt

package kulloveth.developer.com.countrydetails.ui.countrys
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import kulloveth.developer.com.countrydetails.data.CountryDetailsRepository
import kulloveth.developer.com.countrydetails.data.model.CountryDetails
import kulloveth.developer.com.countrydetails.data.model.Language
import kulloveth.developer.com.countrydetails.data.model.Translations
class CountrysViewModel : ViewModel() {
private var countrysLiveData = MutableLiveData<List<CountryDetails>>()
    var translationsLiveData = MutableLiveData<Translations>()
    var languageLiveData = MutableLiveData<List<Language>>()
   fun fetchCountrys(): LiveData<List<CountryDetails>> {
        countrysLiveData = CountryDetailsRepository().countrysLiveData
        return countrysLiveData
    }
   fun setTranslations(translations: Translations) {
        translationsLiveData.value = translations
    }
   fun setLanguages(language: List<Language>) {
        languageLiveData.value = language
    }}

The above class extends the viewmodel class and contains the data to be observed by the view classes. The methods to take note of here is the setTranslations and setLanguages which am going to use to pass data between CountrysFragment where the data is been setup to both the TranslationsFragment and the LanguagesFragment where the data is been observed

MainViewModelFactory.kt

package kulloveth.developer.com.countrydetails.ui
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import kulloveth.developer.com.countrydetails.ui.countrys.CountrysViewModel
class MainViewModelFactory : ViewModelProvider.NewInstanceFactory() {
    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
        return CountrysViewModel() as T
    }
}

activity_main.xml

<androidx.constraintlayout.widget.ConstraintLayout 
   xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:app="http://schemas.android.com/apk/res-auto"
   xmlns:tools="http://schemas.android.com/tools"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   tools:context=".MainActivity">
<fragment
   android:id="@+id/nav_host_fragment"
   android:name="androidx.navigation.fragment.NavHostFragment"
   android:layout_width="0dp"
   android:layout_height="0dp"
   app:layout_constraintLeft_toLeftOf="parent"
   app:layout_constraintRight_toRightOf="parent"
   app:layout_constraintTop_toTopOf="parent"
   app:layout_constraintBottom_toBottomOf="parent"
   app:defaultNavHost="true"
   app:navGraph="@navigation/nav_graph" />
</androidx.constraintlayout.widget.ConstraintLayout>

Three important points to note in the layout above is android:name which contains the class name of your Nav Host implementation, app:navGraph which associates the navHostFragment with a nav-graph where the user destinations are been specified, app:defaultNavHost="true" which ensures that your NavHostFragment intercepts the system Back button.

To add a navigation graph to your project, do the following:
In the Project window, right-click on the res directory and select New > Android Resource File. The New Resource File dialog appears.
Type a name in the File name field, such as "nav_graph".
Select Navigation from the Resource type drop-down list, and then click OK. A navigation resource directory will be automatically created in the res directory and there you find you nav-graph

nav-graph.xml

<?xml version="1.0" encoding="utf-8"?>
 <navigation xmlns:android="http://schemas.android.com/apk/res/android"
 xmlns:app="http://schemas.android.com/apk/res-auto"
 xmlns:tools="http://schemas.android.com/tools"
 android:id="@+id/nav_graph"
 app:startDestination="@id/countrysFragment">
 <fragment
  android:id="@+id/countrysFragment"
android:name="kulloveth.developer.com.countrydetails.ui.countrys.CountrysFragment"
android:label="fragment_countrys"
tools:layout="@layout/fragment_countrys" >
<action
 android:id="@+id/action_countrysFragment_to_detailsFragment"
 app:destination="@id/detailsFragment" />
    </fragment>
    <fragment
        android:id="@+id/detailsFragment"
android:name="kulloveth.developer.com.countrydetails.ui.details.DetailsFragment"
        android:label="fragment_details"
        tools:layout="@layout/fragment_details" >
        <argument
            android:name="countryName"
  app:argType="kulloveth.developer.com.countrydetails.data.model.CountryDetails"
            app:nullable="false" />
<argument
            android:name="countryFlag"
app:argType="kulloveth.developer.com.countrydetails.data.model.CountryDetails"
            app:nullable="false" />
<argument
            android:name="timeZone"
  app:argType="kulloveth.developer.com.countrydetails.data.model.CountryDetails"
            app:nullable="false" />
    </fragment>
</navigation>

In the above nav-graph take note of the tags inside the detailsFragment which contains the details of the data to be received by the fragment. android:name is the String key used to identify a particular data while passing and receiving, app:argType is used to represent what type of data is been passed, app:nullable is used to indicate whether the data can be null or not

MainActivity.kt

package kulloveth.developer.com.countrydetails
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
}}

fragment_countrys.xml

<FrameLayout 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"
    tools:context=".ui.countrys.CountrysFragment">
    <androidx.recyclerview.widget.RecyclerView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/countryRv" />
  </FrameLayout>

CountrysAdapter.kt

package kulloveth.developer.com.countrydetails.ui.countrys
import android.app.Activity
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.ahmadrosid.svgloader.SvgLoader
import kotlinx.android.synthetic.main.rv_list_item.view.*
import kulloveth.developer.com.countrydetails.R
import kulloveth.developer.com.countrydetails.data.model.CountryDetails
class CountrysAdapter : ListAdapter<CountryDetails, CountrysAdapter.MainViewHolder>(
    DiffCallback()
) {
    lateinit var mItemCLicked: ItemCLickedListener
class DiffCallback : DiffUtil.ItemCallback<CountryDetails>() {
        override fun areItemsTheSame(oldItem: CountryDetails, newItem: CountryDetails): Boolean {
            return oldItem.id == newItem.id
        }
override fun areContentsTheSame(oldItem: CountryDetails, newItem: CountryDetails): Boolean {
            return oldItem.id == newItem.id
        }}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MainViewHolder{
        val view = LayoutInflater.from(parent.context).inflate(R.layout.rv_list_item, parent, false)
        return MainViewHolder(view)}
override fun onBindViewHolder(holder: MainViewHolder, position: Int) {
        holder.bind(getItem(position))
        holder.itemView.setOnClickListener {
            mItemCLicked.let {
                mItemCLicked.onItemClicked(getItem(position))}}}
    fun setUpListener(itemCLicked: ItemCLickedListener){
        mItemCLicked = itemCLicked}
 class MainViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
fun bind(countryDetails: CountryDetails) {
            itemView.country.text = countryDetails.name
            SvgLoader.pluck()
                .with(itemView.context as Activity?)
                .setPlaceHolder(R.mipmap.ic_launcher, R.mipmap.ic_launcher)
                .load(countryDetails.flag, itemView.flag)}}
interface ItemCLickedListener {
        fun onItemClicked(countryDetails: CountryDetails)}}

The above class is an adapter class used to setup data for the recyclerView present in the CountrysFragment.kt

rv_list_item.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_margin="8dp"
    app:cardUseCompatPadding="true">
<androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">
<ImageView
            android:id="@+id/flag"
            android:layout_width="wrap_content"
            android:layout_height="100dp"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
<TextView
            android:id="@+id/country"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:gravity="center"
            android:textColor="#08141a"
            app:layout_constraintEnd_toEndOf="@id/flag"
            app:layout_constraintStart_toStartOf="@id/flag"
            app:layout_constraintTop_toBottomOf="@id/flag" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>

The recyclerview item layout used for the adapter viewHolder

CountrysFragment.kt

package kulloveth.developer.com.countrydetails.ui.countrys
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.os.bundleOf
import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.navigation.NavController
import androidx.navigation.Navigation
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.StaggeredGridLayoutManager
import kotlinx.android.synthetic.main.fragment_countrys.*
import kulloveth.developer.com.countrydetails.R
import kulloveth.developer.com.countrydetails.data.model.CountryDetails
import kulloveth.developer.com.countrydetails.ui.MainViewModelFactory
class CountrysFragment : Fragment() {
 val adapter = CountrysAdapter()
    var navController: NavController? = null
    private val viewModelFactory = MainViewModelFactory()
    private lateinit var viewModel: CountrysViewModel
   override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        initViewModel()
    }
     override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.fragment_countrys, container, false)
    }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        navController = Navigation.findNavController(view)
countryRv.layoutManager = StaggeredGridLayoutManager(3, RecyclerView.VERTICAL)
        countryRv.adapter = adapter
    }
fun initViewModel() {
activity.let {
            viewModel = ViewModelProvider(
                requireActivity(),
                viewModelFactory
            ).get(CountrysViewModel::class.java)
            viewModel.fetchCountrys().observe(this, Observer {
                it.forEach {
                    Log.d("nnnn", "" + it.name)
                }
                adapter.submitList(it)
            })
adapter.setUpListener(object : CountrysAdapter.ItemCLickedListener {
                override fun onItemClicked(countryDetails: CountryDetails) {
                    val bundle = bundleOf(
                        "countryName" to countryDetails.name,
                        "countryFlag" to countryDetails.flag,
                        "timeZone" to countryDetails.timezones
                    )
                    viewModel.setTranslations(countryDetails.translations)
                    viewModel.setLanguages(countryDetails.language)
                    navController?.navigate(
                        R.id.action_countrysFragment_to_detailsFragment,
                        bundle
                    )
                    Log.d("cor", "" + countryDetails.name)}})}}}

In the above class, take note of the bundleOf() method where I mapped the countrys name, the countrys flag and the timezone to the respective keys which will be used later to retrieve them in the DetailsFragment,the bundle is passed as a second parameter to the navigate method to carry the data with it while moving to its destination.Also take note of the setTranslations and setLanguages method from the CountrysViewModelclass which is used to set the translations and languages of each countrys and will later be observed from TranslationsFragment and LanguagesFragment respectively.

fragment_details.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".ui.details.DetailsFragment">
<ImageView
        android:id="@+id/flag_img"
        android:layout_width="match_parent"
        android:layout_height="200dp"
        android:layout_marginTop="8dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
<TextView
        android:id="@+id/country_text"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:text="country"
        android:textColor="@color/worldColor"
        android:textSize="24sp"
        android:textStyle="bold"
        app:layout_constraintEnd_toEndOf="@id/flag_img"
        app:layout_constraintStart_toStartOf="@id/flag_img"
        app:layout_constraintTop_toBottomOf="@id/flag_img" />
<TextView
        android:id="@+id/timezone_text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:text="TimeZone"
        android:textColor="@color/worldColor"
        app:layout_constraintEnd_toStartOf="@id/timeZone"
        app:layout_constraintStart_toStartOf="@id/flag_img"
        app:layout_constraintTop_toBottomOf="@id/country_text" />
 <TextView
        android:id="@+id/timeZone"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:text="UTC"
        android:textColor="@color/worldColor"
        app:layout_constraintEnd_toEndOf="@id/flag_img"
        app:layout_constraintStart_toEndOf="@id/timezone_text"
        app:layout_constraintTop_toBottomOf="@id/country_text" />
<com.google.android.material.tabs.TabLayout
        android:id="@+id/tabLayout"
        android:layout_width="match_parent"
        android:layout_height="40dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/timeZone"
        app:tabBackground="@color/worldColor"
        app:tabIndicatorColor="@android:color/white"
        app:tabTextColor="@android:color/white" />
<androidx.viewpager2.widget.ViewPager2
        android:id="@+id/pager"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/tabLayout" />
</androidx.constraintlayout.widget.ConstraintLayout>

ViewPagerAdapter.kt

package kulloveth.developer.com.countrydetails.ui.details
import androidx.fragment.app.Fragment
import androidx.viewpager2.adapter.FragmentStateAdapter
import kulloveth.developer.com.countrydetails.ui.details.languages.LanguagesFragment
class ViewPagerAdapter(fragment: Fragment) : FragmentStateAdapter(fragment) {
    override fun getItemCount(): Int = 2
     override fun createFragment(position: Int): Fragment = when (position) {
     0 -> TranslationsFragment()
        1 -> LanguagesFragment()
        else -> throw IllegalStateException("Invalid adapter position")
    }}

The above class is the adapter class for ViewPager2 present in the DetailsFragment which contains two other fragments.

DetailsFragment.kt

package kulloveth.developer.com.countrydetails.ui.details
import android.app.Activity
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import com.ahmadrosid.svgloader.SvgLoader
import com.google.android.material.tabs.TabLayoutMediator
import kotlinx.android.synthetic.main.fragment_details.*
import kulloveth.developer.com.countrydetails.R
class DetailsFragment : Fragment() {
private lateinit var viewPagerAdapter: ViewPagerAdapter
    var countryName: String? = null
    private var countryFlag: String? = null
    var timeZon: ArrayList<String>? = null
 override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        countryName = arguments?.getString("countryName")
        countryFlag = arguments?.getString("countryFlag")
        timeZon = arguments?.getStringArrayList("timeZone")
    }
override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.fragment_details, container, false)
    }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        Log.d("corn", "" + countryName)
country_text.text = countryName
        timeZon?.forEach {
            timeZone.text = it
        }
SvgLoader.pluck()
            .with(context as Activity?)
            .setPlaceHolder(R.mipmap.ic_launcher, R.mipmap.ic_launcher)
            .load(countryFlag, flag_img)
viewPagerAdapter = ViewPagerAdapter(this)
        pager.adapter = viewPagerAdapter
        TabLayoutMediator(tabLayout, pager) { tab, position ->
            when (position) {
                0 -> tab.text = "Translation"
                1 -> tab.text = "Languages"
            }}.attach()
   }}

In the code above observe the onCreate() method where the data is been received by passing its type of data with its key to arguments. countrys name and flag are both strings so getString method is used, timezone is a list so getStringArrayList is used.

fragment_translations.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    tools:context=".ui.details.TranslationsFragment">
<TextView
        android:id="@+id/translation_tv"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp"
        android:gravity="center"
        android:text="Translations"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
<TextView
        android:id="@+id/german_tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp"
        android:text="German"
        app:layout_constraintEnd_toStartOf="@id/germanTrans"
        app:layout_constraintStart_toStartOf="@id/translation_tv"
        app:layout_constraintTop_toBottomOf="@id/translation_tv" />
<TextView
        android:id="@+id/germanTrans"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp"
        android:text="GermanTrans"
        app:layout_constraintEnd_toEndOf="@id/translation_tv"
        app:layout_constraintStart_toEndOf="@id/german_tv"
        app:layout_constraintTop_toBottomOf="@id/translation_tv" />
<TextView
        android:id="@+id/spanish_tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp"
        android:text="Spanish"
        app:layout_constraintEnd_toStartOf="@id/spanishTrans"
        app:layout_constraintStart_toStartOf="@id/translation_tv"
        app:layout_constraintTop_toBottomOf="@id/german_tv" />
<TextView
        android:id="@+id/spanishTrans"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp"
        android:text="SpanishTrans"
        app:layout_constraintEnd_toEndOf="@id/translation_tv"
        app:layout_constraintStart_toEndOf="@id/spanish_tv"
        app:layout_constraintTop_toBottomOf="@id/german_tv" />
<TextView
        android:id="@+id/french_tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp"
        android:text="French"
        app:layout_constraintEnd_toStartOf="@id/frenchTrans"
        app:layout_constraintStart_toStartOf="@id/translation_tv"
        app:layout_constraintTop_toBottomOf="@id/spanish_tv" />
<TextView
        android:id="@+id/frenchTrans"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp"
        android:text="FrenchTrans"
        app:layout_constraintEnd_toEndOf="@id/translation_tv"
        app:layout_constraintStart_toEndOf="@id/french_tv"
        app:layout_constraintTop_toBottomOf="@id/spanish_tv" />
<TextView
        android:id="@+id/japan_tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp"
        android:text="Japan"
        app:layout_constraintEnd_toStartOf="@id/japanTrans"
        app:layout_constraintStart_toStartOf="@id/translation_tv"
        app:layout_constraintTop_toBottomOf="@id/french_tv" />
<TextView
        android:id="@+id/japanTrans"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp"
        android:text="JapanTrans"
        app:layout_constraintEnd_toEndOf="@id/translation_tv"
        app:layout_constraintStart_toEndOf="@id/japan_tv"
        app:layout_constraintTop_toBottomOf="@id/french_tv" />
<TextView
        android:id="@+id/italian_tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp"
        android:text="Italian"
        app:layout_constraintEnd_toStartOf="@id/italianTrans"
        app:layout_constraintStart_toStartOf="@id/translation_tv"
        app:layout_constraintTop_toBottomOf="@id/japan_tv" />
<TextView
        android:id="@+id/italianTrans"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp"
        android:text="ItalianTrans"
        app:layout_constraintEnd_toEndOf="@id/translation_tv"
        app:layout_constraintStart_toEndOf="@id/italian_tv"
        app:layout_constraintTop_toBottomOf="@id/japan_tv" />
<TextView
        android:id="@+id/persian_tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp"
        android:text="Persian"
        app:layout_constraintEnd_toStartOf="@id/persianTrans"
        app:layout_constraintStart_toStartOf="@id/translation_tv"
        app:layout_constraintTop_toBottomOf="@id/italian_tv" />
<TextView
        android:id="@+id/persianTrans"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="8dp"
        android:text="PersianTrans"
        app:layout_constraintEnd_toEndOf="@id/translation_tv"
        app:layout_constraintStart_toEndOf="@id/persian_tv"
        app:layout_constraintTop_toBottomOf="@id/italian_tv" />
</androidx.constraintlayout.widget.ConstraintLayout>

TranslationsFragment.kt

package kulloveth.developer.com.countrydetails.ui.details
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.Observer
import kotlinx.android.synthetic.main.fragment_translations.*
import kulloveth.developer.com.countrydetails.R
import kulloveth.developer.com.countrydetails.ui.countrys.CountrysViewModel
class TranslationsFragment : Fragment() {
private val viewModel: CountrysViewModel by activityViewModels()
    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.fragment_translations, container,false)}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
 viewModel.translationsLiveData.observe(viewLifecycleOwner, Observer {
            germanTrans.text = it.de
            spanishTrans.text = it.es
            frenchTrans.text = it.fr
            japanTrans.text = it.ja
            italianTrans.text = it.it
            persianTrans.text = it.fa})}}

TranslationsFragment is one of the fragments on the viewpager, the translations which was created in CountrysViewModel.kt and setup in CountrysFragment.kt is now been observed in this fragment with the code viewmodel.translationsLiveData.observe{}.

fragment_languages.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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"
    tools:context=".ui.details.languages.LanguagesFragment">
  <androidx.recyclerview.widget.RecyclerView
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:id="@+id/recyclerView"/>
</FrameLayout>

languages_item.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    xmlns:app="http://schemas.android.com/apk/res-auto">
<TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/name_tv"
        app:layout_constraintTop_toTopOf="parent"
        android:layout_marginTop="8dp"
        app:layout_constraintEnd_toStartOf="@id/name"
        app:layout_constraintStart_toStartOf="parent"
        android:text="Name" />
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/name"
        app:layout_constraintTop_toTopOf="parent"
        android:layout_margin="8dp"
        app:layout_constraintStart_toEndOf="@id/name_tv"
        android:text="@string/hello_blank_fragment" />
<TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/nativeName_tv"
        android:layout_marginTop="8dp"
        app:layout_constraintTop_toBottomOf="@id/name_tv"
        app:layout_constraintStart_toStartOf="parent"
        android:text="Native name:" />
<TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/nativeName"
        android:layout_margin="8dp"
        app:layout_constraintTop_toBottomOf="@id/name"
        app:layout_constraintStart_toEndOf="@id/nativeName_tv"
        android:text="@string/hello_blank_fragment" />
</androidx.constraintlayout.widget.ConstraintLayout>

LanguageAdapter.kt

package kulloveth.developer.com.countrydetails.ui.details.languages
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.synthetic.main.languages_item.view.*
import kulloveth.developer.com.countrydetails.R
import kulloveth.developer.com.countrydetails.data.model.Language
class LanguageAdapter : ListAdapter<Language, LanguageAdapter.MainViewHolder>(
    DiffCallback()) {
 class DiffCallback : DiffUtil.ItemCallback<Language>() {
        override fun areItemsTheSame(oldItem: Language, newItem: Language): Boolean { return oldItem.id == newItem.id}
override fun areContentsTheSame(oldItem: Language, newItem: Language): Boolean {
            return oldItem.id == newItem.id}}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MainViewHolder {val view = LayoutInflater.from(parent.context).inflate(R.layout.languages_item, parent, false)
        return MainViewHolder(
            view)}
override fun onBindViewHolder(holder: MainViewHolder, position: Int) {
        holder.bind(getItem(position))}
 class MainViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
fun bind(language: Language) {
            itemView.name.text = language.name
            itemView.nativeName.text = language.nativeName
}}}

The LanguagesFragment uses a recyclerview and the above is its adapter

LanguagesFragment.kt

package kulloveth.developer.com.countrydetails.ui.details.languages
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.Observer
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.synthetic.main.fragment_languages.*
import kulloveth.developer.com.countrydetails.R
import kulloveth.developer.com.countrydetails.ui.countrys.CountrysViewModel
class LanguagesFragment : Fragment() {
val adapter = LanguageAdapter()
    val viewModel: CountrysViewModel by activityViewModels()
    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.fragment_languages, container, false)
    }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        recyclerView.layoutManager = LinearLayoutManager(requireActivity())
        recyclerView.adapter = adapter
        recyclerView.addItemDecoration(DividerItemDecoration(requireContext(), DividerItemDecoration.VERTICAL))
activity.let {
            viewModel.languageLiveData.observe(viewLifecycleOwner, Observer {
                adapter.submitList(it)
            })}}}

In the above code the languageLiveData is also been observed to access the language list which was also setup in the CountrysFragment.
Also note that in each of these fragment that is receiving the data using viewmodel, the viewmodel class was initialized using activityViewModels() this is to ensure that these fragments are retrieving the activity that contains them.

Summary
In this post we talked about navigation component, how you can pass data with Bundle between fragments when using the component and also how you can pass data between fragments using viewmodel. I hope this helps you out in solving related problems, you can drop your feedback and questions in the comment section LOVETH
The github link to the full code is

GitHub logo kulloveth / CountryDetails

List of Countries and all possible information about them

CountryDetails

This project is based on an Api from restcountries.eu to get List of Countries and all possible information about them

IDE-Integrated Development Environment

  • Android Studio- Find link on how you can setup Android studio here

Libraries used and their documentation

Screenshot

nav_graph

Screenshots

countrys

translation

languages

Posted on by:

kulloveth profile

Loveth Nwokike

@kulloveth

Android Engineer, Senior Apprentice :)

Discussion

markdown guide
 

Nice article.. clear explanation of code. Have one doubt. in MVVM architecture, can fragments have the responsibility to directly navigate to other fragment, or should the hosting activity take that responsibility of communication between fragments?