Photo by Marvin Meyer on Unsplash
Implementing NavGraph & Databinding in our App with MVVM & DI in Android: (Day 09)
Now that we have already implemented the ViewModel and Model of our MVVM-based app, the next and the last missing piece of the puzzle is the 'View', which is precisely what we'll be implementing in this blog post.
The view simply put is nothing but the UI components that we'll provide the app user to interact with the application, so for e.g. in our case or login and signup flow, the pages or forms where the user will enter their e-mail ID, password, etc. will be considered the view.
In our project, we'll have two views i.e. a Login Page and a Signup Page. We'll use two fragments to implement it, one for each of the pages. Both of these fragments will be child to the same Activity which we'll call the AuthenticationActivity.
To create these fragments and connect them with their respective XML layout files, we'll be using data binding.
Data Binding
Data Binding in Android allows you to connect your app data directly to the user interface without having to write extra code to update the UI every time the data changes. You can set up this connection through a special format in your layout XML files. When your data changes, the UI updates automatically, and vice versa. This makes your code cleaner, easier to manage, and can also make your app run faster.
We'll be exploring these concepts in much detail as we move ahead in our series of blog posts. For now, let's start the implementation of a basic signup layout and connect it with a fragment.
The code to this blog post can be found here
Step 1
First, create a basic signup_fragment_layout
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Click"
android:id="@+id/click"/>
</LinearLayout>
</layout>
We have wrapped our layout inside an XML tag layout
here, because it tells Android Studio to create the necessary classes to help in data binding.
Step 2
Now, let's go ahead and create a fragment and name it SignUpFragment
class SignUpFragment : DaggerFragment() {
private lateinit var binding: SignupFragmentLayoutBinding //created because we wrapped our layout file inside <layout> XML tag
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?,
): View {
binding = DataBindingUtil.inflate(
inflater,
R.layout.signup_fragment_layout,
container,
false
)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.click.setOnClickListener {
}
}
}
Explanation
An important thing to note here is that the SignupFragmentLayoutBinding
class was created because our XML layout file was wrapped inside <layout>
XML tag. The name of these classes is generally the name of the XML file followed by the word binding. So for e.g. our layout file was named signup_fragment_layout.xml
hence the resultant created Kotlin file was SignupFragmentLayoutBinding
.
Another important thing to note here is that, now we can directly access all the components inside our layout, without creating objects for each of them separately (notice that the click
button object was never created, it was already present inside the binded class)
Step 3
Now, let's go ahead and implement our viewmodel and inject required dependencies as we did for our Activity class earlier.
class SignUpFragment : DaggerFragment() {
private lateinit var binding: SignupFragmentLayoutBinding
@Inject
lateinit var factory: ViewModelFactory
private val signupViewModel: SignUpViewModel by viewModels({requireActivity()}) { factory }
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?,
): View {
binding = DataBindingUtil.inflate(
inflater,
R.layout.signup_fragment_layout,
container,
false
)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
initViewModel()
binding.click.setOnClickListener {
fetchUserDataList()
}
}
private fun initViewModel(){
signupViewModel.let{
it.userDataResponse.observe(viewLifecycleOwner, Observer {userDataResponse ->
Log.e("Observed", "Some data received in observer ${Gson().toJson(userDataResponse)}")
})
}
}
fun storeDataExample(){
val data = UserData()
data.emailId = "testemail@test.com"
data.firstName = "test name"
data.password = "test password"
data.uid = "12312"
CoroutineScope(Dispatchers.IO).launch {
signupViewModel.storeUserData(userData = data)
}
}
fun fetchUserDataList(){
signupViewModel.fetchAllUserData()
}
}
Nothing new has been implemented here, all we have done is inject our ViewModel into the fragment and then call ViewModel methods, and observe changes to userDataResponse
as we did earlier in our MainActivity
code.
Step 4
Now we need to attach this fragment to a parent activity and for this, we'll be using navigation graphs.
Navigation Graphs
The Navigation Graph in Android development is a visual representation or a structured XML file that maps out the navigation between different screens (destinations) in an app. It includes actions that define pathways between destinations, and arguments for data transfer, and can also incorporate deep links for external navigation. It's part of the Navigation Component, aiding developers in managing app navigation in a more organized and less error-prone way, with the help of a visual editor provided by Android Studio.
Step 4a
Let's start by creating a navigation resource directory. To do this, go to res
folder, right-click, and create a new resource directory as shown below.
This new directory will be of type navigation
.
Step 4b
Now that we have created a navigation directory, the next step would be creating a navigation resource file as shown below.
Name this file as login_signup_navigation.xml
Step 4c
Let's go ahead and create a basic activity_authentication.xml
next.
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<fragment
android:id="@+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navGraph="@navigation/login_signup_navigation"
/>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</layout>
Explanation
We are here utilizing Android's Data Binding, Navigation Component, and CoordinatorLayout to create a structured UI and navigation flow. Here's a breakdown of key elements and attributes in the code:
<layout>
Element:- This is the root element for the XML file when using Data Binding.
<fragment>
Element:- Represents a fragment, which is a portion of UI in an activity.
android:id="@+id/nav_host_fragment"
:- Assign an ID to this fragment for referencing it within the code.
android:name="androidx.navigation.fragment.NavHostFragment"
:- Specifies the type of fragment - in this case, a NavHostFragment which serves as a container for navigating other fragments.
app:defaultNavHost="true"
:- This makes the NavHostFragment act as the default host for navigation within this layout.
app:navGraph="@navigation/login_signup_navigation"
:- Specifies the navigation graph resource, which defines the navigation structure for this fragment. The
login_signup_navigation
graph contains the destinations and actions for navigating between screens within this portion of the app.
- Specifies the navigation graph resource, which defines the navigation structure for this fragment. The
Okay, so up till now we have created a navigation graph, that will help us route between different fragments, and we have referenced this graph in our authentication activity's layout file.
Step 5
Now let's create an AuthenticationActivity
class and bind our XML layout file to it.
class AuthenticationActivity: DaggerAppCompatActivity() {
lateinit var binding: ActivityAuthenticationBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_authentication)
}
}
PS: Do not forget to mention this activity into the AppModule
class so that it's available to our dagger graph.
Step 6
Now that we have everything set, the last step would be adding our SignUpFragment to this Navigation Graph. To do this, modify the navigation graph code to look like this.
<?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/login_signup_navigation"
app:startDestination="@id/signupFragment">
<fragment
android:id="@+id/signupFragment"
android:name="com.example.understandingmvvm.SignUpFragment"
android:label="SignupFragment"
tools:layout="@layout/signup_fragment_layout"/>
</navigation>
This can also be done via the add destination button which you can find in the design
View of the Navigation Graph.
Step 7
Now that everything is setup, just change the launcher activity from MainActivity
to AuthenticationActivity
and then install the application.
Result
The app functionally should still look exactly the same as it looked into our previous blog post, but a major difference now is that we are a Fragment bind to a ViewModel to update our UI and call repository methods instead of directly calling from an Activity using NavGraph.
Conclusion
While this might look like a lot of work just to streamline a relatively simple feature, as we move ahead and start implementing the real login and signup workflows with multiple fragments and data flow streams, you'll realize how NavGraph and Fragments streamline the whole process greatly.
If this blog post was a little confusing do not worry, we'll be exploring NavGraph, Databinding, and Clean Architecture concepts in our next blog post which will help you better understand the use case of these libraries.
Till then, happy coding!