Jetpack Components

Android Architecture Components

  • View Model
  • Work Manager
  • Live Data
  • Room
  • Data Binding
  • Navigation
  • Paging
These help us in creating robust, testable, maintainable and has less boilerplate code
Android jetpack is a collection of libraries that makes it easier for us to develop great android application

ViewModel : 

  • Survives configuration changes
  • Not same as onSavedInstanceState() : For small data like a string or integer
  • ViewModel for large data bitmap, user list etc.
  • Stores and manages the UI related data
  • Gets destroyed only when the activity gets destroyed in onCleared()
  • Communication layer between UI and DB
  • Manages the UI related data in a lifecycle conscious way
  • The ViewModel prepares the data which is sent to the UI. So when the 

Advantages : 
There are other various advantages of using ViewModel class provided by Android framework like:
Handle configuration changes: ViewModel objects are automatically retained whenever activity is 
recreated due to configuration changes.
Lifecycle Awareness: ViewModel objects are also lifecycle-aware. They are automatically cleared when the Lifecycle they are observing gets permanently destroyed.
Data Sharing: Data can be easily shared between fragments in an activity using ViewModels.
Kotlin-Coroutines support: ViewModel includes support for Kotlin-Coroutines. So, they can be easily integrated for any asynchronous processing

Implementation :

  • Extends the class with ViewModel
  • Gets destroyed when the activity gets destroyed
  • Orientation change has no affect on the data
  • Calls onCleared() when it gets destroyed

Live Data :

  • Is an observable data holder class and lifecycle aware component
  • It keeps the data and allows the data to be observed
  • We need to observe the live data from app components onCreate() method so that system doesn't do redundant calls for observing the livedata
  • Also it ensures that the activity will have the updated data at this stage
  • Its aware if the component that's attached to it is active or not
  • Helps us in notify the views when the underlying database data changes
Types :
  1. LiveData
  2. MutableLiveData
  3. MediatorLiveData

LiveData is immutable by default. By using LiveData we can only observe the data and cannot set the data.

MutableLiveData is mutable and is a subclass of LiveData. In MutableLiveData we can observe and set the values using postValue() and setValue() methods (the former being thread-safe) so that we can dispatch values to any live or active observers.

MediatorLiveData can observe other LiveData objects such as sources and react to their onChange() events. MediatorLiveData will give us control over when we want to perform an action in particular or when we want to propagate an event.

LiveData will trigger upon configuration change or screen rotation. But in some scenarios, like when we need to perform UI updates by clicking on a particular View to perform validations or show a progress bar during server call, we go with SingleLiveEvent.

SingleLiveEvent is a subclass of MutableLiveData. It is aware of the View's lifecycle and can observe the data with a single Observer

Shared Flow and State Flow
A StateFlow takes a default value through the constructor and emits it immediately while a SharedFlow takes no value and emits nothing by default.

Advantages : 

1. No memory leaks cos of lifecycle awareness

    Observers are bound to lifecycle objects and cleanup themselves when their associated lifecycle is destroyed

2. Always UI has updated data

    if lifecycle becomes inactive, it receives the data upon becoming active again. If the activity is in background, it will receive the update when it comes to foreground.

3. Manages Configuration Changes

    If activity is recreated due to orientation changes, it receives the updated data immediately with the help of ViewModel

4. No crash due to stopped activities

    If the observers lifecycle is inactive, such as if activity is in back stack, it doesn't receive any LiveData events

5. Ensures the UI matches the data

    LiveData notifies the observer object when the lifecycle state changes. Instead of updating the UI every time the app data changes, Observer can change the UI when there is a change

Benefits

  • Automatically gets destroyed when the associated LifeCycleOwner gets destroyed
  • Can be shared by multiple resources since its not observed when the activity is stopped
  • LiveData is Best when used with ViewModel

Implementation :

Survives configuration changes

Observe the LiveData in onCreate() ensures system doesn't do redundant calls for observing the LiveData

Define the MutableLiveData in the ViewModel

Define the LiveData of the same data type as that of MutableLiveData in the activity to observe the changes in data

eg : 

In ViewModel :

private MutableLiveData<String> randomNumber = new MutableLiveData<>();

randomNumber.setValue("Number "+ random.nextInt(10 - 1)+1);

In Activity :

LiveData<String> myNumber = model.getNumber();

myNumber.observe(this, new Observer<String>() {

@Override

public void onChanged(String s) {

tv.setText(s);

Log.i(TAG, "In OnChanged");

}

});

Corner Case : 

Suppose we are calling an API from 1st activity and we r moving to next activity.

Now the 1st activity is in the stop state and the live data will not be observed but the ViewModel of that activity will have the updated data

So we can conclude that the live data can be observed when the activity that s observing it is in the active or resumed state

Room :

  • Is an abstraction layer over SQLite
  • Is an ORM(Object Relation Mapping) library
  • It easily converts SQLite table data to java objects
  • It provides compile time check of SQLite statements and returns the LiveData observable

Components of Room : 

Entity : 

  1. Defines the schema of the DB table which has the getter and setter functions
  2. Transforms the object to a table to store data
  3. Its annotated by @Entity
  4. Represents the table in our database
    @Entity(tableName = "user_details")
    data class User{

    @PrimaryKey
    @NonNull
    var uid : Int

    @NonNull
    @ColumnInfo(name = "first_name")
    var firstName : String?
    }

DAO :

  1. Data Access Objects
  2. Contains the methods to access database
  3. Annotated by @DAO
  4. Its an interface
  5. For every Entity, a DAO interface is needed that contains functions for accessing the database
        @Dao
        interface UserDao {
            @Query("select * from User")
            fun getAll() : List<User> 
        }

Database :

  1. Database holder class and serves as an main access point to the underlying connection of our apps persistent and relational data
  2. Annotated with @Database
  3. The class need to be an abstract class and should extend the RoomDatabase class
  4. Include the list of entities and mention the version number
  5. It should have abstract method of each DAO that's related to it
  6. Create the instance of RoomDatabase using Room.databaseBuilder() and it has to be singleton
        @Database(entities = arrayOf(User :: class), version = 1)
        abstract class AppDatabase : RoomDatabase() {
            abstract fun userDao() : UserDao
        }


Difference between Room and SQLite Database : 

  1. Sqlite deals with raw queries whereas Room doesn't require raw queries
  2. No Compile time verification. Room has compile time verification of raw SQL queries
  3. More boilerplate code required to convert from SQL queries to java data objects whereas in Room it maps database objects to java objects with minimal boilerplate code
  4. SQLite API are low level. So more time and effort is required to build apps whereas Room when used with ViewModel and LiveData makes it easy
  5. Updating database, changing schema was difficult, Easy in Room with Migration classes
  6. Cannot work with LiveData and ViewModel, Room is designed to work with LiveData and ViewModel

Singleton class for Room Database

class CustomDatabase private constructor() {

var quoteDao = FakeQuoteDao()
private set

companion object {
@Volatile private var instance : CustomDatabase? = null

// Already instantiated? - return the instance
// Otherwise instantiate in a thread-safe manner
fun getInstance() = instance?: synchronized(this) {
// If it's still not instantiated, finally create an object
// also set the "instance" property to be the currently created one
instance?: CustomDatabase().also { instance = it }
}
}
}

Implementing Room Components in application

  • Android application uses DAO to interact with DB
  • App uses DAO object to perform DB related operations
  • This includes getting entities from DB or persisting the changes back to the DB
  • Our application uses the entities to get or set the field value 

Inserting data to Room DB :

Create new activity and a layout file
Add the fields in the entity
Add the constructor and getter() in the entity
Declare the method in DAO to insert note
Create the wrapper method for insert operation in ViewModel
Make it work on Non UI thread using AsyncTask
Connect UI to DB

Fetching the DATA from DB and Displaying it :

Add the RecyclerView and setup the adapter for the MainActivity
Declare getAllNotes() in DAO to fetch the notes with SQL Query, which returns LiveData
Add the @Query annotation with SQL statement which returns LiveData
Add member variables for List of work in ViewModel and initialize in constructor
Add getter method to get all notes in ViewModel
Observe the list of notes in MainActivity

Work Manager : 

Used to manage the android background jobs
Work Manager is part of Androidx package also known as Jetpack
Its an API that makes it easy to schedule deferrable, asynchronous tasks that are expected to run reliably
Deferrable - Scheduled Mechanism like if it has to run one time, repetitive or Compatible in DozeMode or Power Saving Mode.
Reliably - Run under constraints ie. Run only when the device is connected to Wi-Fi, When the device is idle, When it has sufficient storage space etc,
Always finish the started work - Even if the App exits, Even if the device restarts

Implementation
API - Worker
We need to extend the Worker Class. We need to implement some callback methods namely doWork() which gets executed when we start the work which returns the Result object(can be esult.success, Result.Failure, Result.Retry), onStopped() gets called when the work gets cancelled or stopped but is optional. If we need to do something when the work is done then we can do them in onStopped()
API - Constraint
We define the various constraints under which we want our task to run like Network Type(Data or Wi-Fi),
BatteryNotLow, Requires Charging, DeviceIdle, StorageNotLow
API - WorkRequest
OneTimeWorkRequest, PeriodicWorkRequest
API - WorkManager
Enqueueing the work, Cancelling the work
WorkParameters - is a mechanism using which we can pass any value to worker class

WorkManager workManager = WorkManager.getInstance(getAppContext())
WorkRequest workRequest = new PeriodicWorkRequest().Builder(WorkerClass, Periodicity, TimeUnit.Minutes).build()
To start the work, we need to call 
workManager.enqueue(workRequest)
To stop the work, we need to call 
workManager.cancelWorkById(workRequest.getId())

WorkChaining
WorkChaining is not possible with PeriodicWorkRequest cos of complicated race conditions that it may induce.
Work1 -> Work2 -> Work3
WorkRequest workRequest1 = new OneTimeWorkRequest().Builder(WorkerClass1).addTag("Worker1").build()
WorkRequest workRequest2 = new OneTimeWorkRequest().Builder(WorkerClass2).addTag("Worker2").build()
WorkRequest workRequest3 = new OneTimeWorkRequest().Builder(WorkerClass3).addTag("Worker3").build()

While calling them
workManager.getInstance(getApplicationContext()).beginWith(workRequest1).then(workRequest2).then(workRequest3).enqueue();

To cancel/stop a work,
workManager.cancelAllWorkByTag("Worker3") -> Worker3 is the tag

Work1, Work2(Together) -> Work3
While calling them
workManager.getInstance(getApplicationContext()).beginWith(Arrays.asList(workRequest1, workRequest2)).then(workRequest3).enqueue();

Consequences : 
If we start the workManager and cancel W3, then W1 & W2 will run properly but W3 will not run and we have invoked cancellation of W3.
If we start the workManager and cancel W2, then W1 will run properly & W2 will stop but W3 will not execute. The work chaining makes sure that if we have established a dependency of the order in which they have to get executed and if 1 of then fails then the further works which have to get executed will be squashed.

Long Running Tasks : 
How to trigger the notification when the worker starts?
ForegroundInfo - Object that holds the notification that we want to trigger. We need to create and use it in our worker. The setForegroundAsync method is called typically inside doWork()
setForegroundAsync(
    ForegroundInfo
);
In Manifest File, we need to mention
<service 
    android:name="androidx.work.impl.foreground.SystemForegroundService"
    android:foregroundServiceType="location|microphone|dataSync"
    tools:node="merge" />

Background Processing : 
They can be like guaranteed execution or execution at the exact time(Like whatsapp data backup) or deferrable tasks(If possible execute else need not). Restarting a failed worker is not possible



Data Binding
Helps us to bind the observable data to UI elements
Data Binding declaratively binds observable data to the UI elements
The Data Binding Library is a support library that allows us to bind UI components in our layouts to data sources in our app using a declarative format

Benefits :

  • Improves the performance of the app
  • Eliminates the use of findViewById() which makes the code concise, easier to read and maintain.
  • Recognizes errors during compile time
  • Helps us keep the code organized
  • Reduces boilerplate code

In build.gradle, under the android tab, add

buildFeatures {
dataBinding true
}
plugins {
    id 'kotlin-kapt'
}
annotationProcessor 'androidx.databinding:databinding-compiler:3.2.0-alpha16'

In activity_main.xml,
we need to make <layout> as the parent/root layout which says the android framework that this layout is using Data Binding. A binding class is automatically generated for each layout file. By default the name of the class is based on the name of the layout file. The name of the class we be the name of the layout activity in Pascal Case with Binding added as suffix
Eg: activity_main.xml -->ActivityMainBinding
activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<layout>

<data>
<variable
name="contact"
type="com.keshav.jetpack_udemy.Contact" />

<variable
name="event"
type="com.keshav.jetpack_udemy.EventHandler" />
</data>

<LinearLayout
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"
android:orientation="vertical"
tools:context=".MainActivity">

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

<TextView
android:id="@+id/tvName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="25sp"
android:text="@{contact.name}" />

<TextView
android:id="@+id/tvEmail"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="20sp"
android:text="@{contact.email}" />

<Button
android:id="@+id/btnClick"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="20sp"
android:text="Click Here"
android:onClick="@{() -> event.onButtonClick()}"/>

</LinearLayout>
</LinearLayout>
</layout>

MainActivity.kt

package com.keshav.jetpack_udemy

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.databinding.DataBindingUtil
import com.keshav.jetpack_udemy.databinding.ActivityMainBinding

class MainActivity : AppCompatActivity() {

private lateinit var binding: ActivityMainBinding

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)

// Using Data Binding without defining the <data> and <variable> in XML file
binding.tvName.text = "Keshav Pai"
binding.tvEmail.text = "keshav.pai87@gmail.com"

// Using Data Binding after defining the <data> and <variable> in XML file
binding.contact = Contact("Keshav Pai", "keshav.pai87@gmail.com")
binding.event = EventHandler(this)
}
}

EventHandler.kt

package com.keshav.jetpack_udemy

import android.content.Context
import android.widget.Toast

open class EventHandler(context : Context) {

private val myContext : Context = context

fun onButtonClick() {
Toast.makeText(myContext, "Button Clicked", Toast.LENGTH_SHORT).show()
}
}

Data Class Contact.kt

package com.keshav.jetpack_udemy

data class Contact(var name : String, var email : String)

Working with Observable Data Objects
Observability refers to the capability of an object to notify others about the changes in its data and data binding library allows us to make the objects observable. Data binding library provides the BaseObservable class which implements the listener registration mechanism. When the contact class inherits from BaseObservable class, it will be responsible for notifying when its properties changes. To do that we will use the @Bindable. To make the contact object observable, we will make the class extend BaseObservable.

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<layout>

<data>
<variable
name="contact"
type="com.keshav.jetpack_udemy.Contact" />

<variable
name="event"
type="com.keshav.jetpack_udemy.EventHandler" />
</data>

<LinearLayout
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"
android:orientation="vertical"
tools:context=".MainActivity">

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

<TextView
android:id="@+id/tvName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="25sp"
android:text="@{contact.name}" />

<TextView
android:id="@+id/tvEmail"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="20sp"
android:text="@{contact.email}" />

<EditText
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/etName"
android:ems="10"
android:inputType="text"
android:text="@={contact.name}"/>

<Button
android:id="@+id/btnClick"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="20sp"
android:text="Click Here"
android:onClick="@{() -> event.onButtonClick(contact.name)}"/>

</LinearLayout>
</LinearLayout>
</layout>

EventHandler.kt

package com.keshav.jetpack_udemy

import android.content.Context
import android.widget.Toast

open class EventHandler(context : Context) {

private val myContext : Context = context

fun onButtonClick(name : String) {
Toast.makeText(myContext, "$name Button Clicked", Toast.LENGTH_SHORT).show()
}
}

Contact.kt

package com.keshav.jetpack_udemy

import androidx.databinding.BaseObservable
import androidx.databinding.Bindable

data class Contact(var _name : String, var _email : String) : BaseObservable() {

@get:Bindable
var name : String = _name
set(value) {
field = value
notifyPropertyChanged(BR.name)
}
@get : Bindable
var email : String = _email
set(value) {
field = value
notifyPropertyChanged(BR.email)
}
}

Loading Image from URL using Glide
Glide Dependency

implementation 'com.github.bumptech.glide:glide:4.13.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.13.0'

activtiy_main.xml

<?xml version="1.0" encoding="utf-8"?>
<layout>

<data>
<variable
name="contact"
type="com.keshav.jetpack_udemy.Contact" />

<variable
name="event"
type="com.keshav.jetpack_udemy.EventHandler" />

<variable
name="imageUrl"
type="String" />
</data>

<LinearLayout
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"
android:orientation="horizontal"
tools:context=".MainActivity">

<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="5dp" >

<ImageView
android:id="@+id/ivProfileImage"
android:layout_width="100dp"
android:layout_height="100dp"
android:padding="5dp"
app:profileImage = "@{imageUrl}"
android:src="@mipmap/ic_launcher_round" />
</LinearLayout>

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

<TextView
android:id="@+id/tvName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="25sp"
android:text="@{contact.name}" />

<TextView
android:id="@+id/tvEmail"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="20sp"
android:text="@{contact.email}" />

<EditText
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/etName"
android:ems="10"
android:inputType="text"
android:text="@={contact.name}"/>

<Button
android:id="@+id/btnClick"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="20sp"
android:text="Click Here"
android:onClick="@{() -> event.onButtonClick(contact.name)}"/>

</LinearLayout>
</LinearLayout>
</layout>

MainActivity.kt

package com.keshav.jetpack_udemy

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.databinding.DataBindingUtil
import com.keshav.jetpack_udemy.databinding.ActivityMainBinding

class MainActivity : AppCompatActivity() {

private lateinit var binding: ActivityMainBinding

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)

// Using Data Binding without defining the <data> and <variable> in XML file
binding.tvName.text = "Keshav Pai"
binding.tvEmail.text = "keshav.pai87@gmail.com"

// Using Data Binding after defining the <data> and <variable> in XML file
binding.contact = Contact("Keshav Pai", "keshav.pai87@gmail.com")
binding.event = EventHandler(this)
binding.imageUrl = "https://i.redd.it/lhw4vp5yoy121.jpg"
}
}

Contact.kt

package com.keshav.jetpack_udemy

import android.widget.ImageView
import androidx.databinding.BaseObservable
import androidx.databinding.Bindable
import androidx.databinding.BindingAdapter
import com.bumptech.glide.Glide

data class Contact(var _name : String, var _email : String) : BaseObservable() {

@get:Bindable
var name : String = _name
set(value) {
field = value
notifyPropertyChanged(BR.name)
}
@get : Bindable
var email : String = _email
set(value) {
field = value
notifyPropertyChanged(BR.email)
}

companion object {
@JvmStatic @BindingAdapter("profileImage")
fun loadImage(view : ImageView, imageUrl : String ) {
Glide.with(view.context)
.load(imageUrl)
.into(view)
}
}
}



In an Activity :

class MainActivity : AppCompatActivity() {

private lateinit var binding : ActivityMainBinding

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

binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
binding.btnRoll.setOnClickListener {
rollDice()
}
}

private fun rollDice() {
val data = when((1..6).random()) {
1 -> R.drawable.dice_1
2 -> R.drawable.dice_2
3 -> R.drawable.dice_3
4 -> R.drawable.dice_4
5 -> R.drawable.dice_5
else -> R.drawable.dice_6
}
binding.imgDice.setImageResource(data)
}
}

In a Fragment :

class HomeFragment : Fragment() {

override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val binding = DataBindingUtil.inflate<FragmentHomeBinding>(inflater,
R.layout.fragment_home, container, false)
return binding.root
}
}


Paging
Used to gradually load the information on demand from the data source

Lifecycle Aware Components
Helps us in managing the activity and fragment life cycle
Lifecycle object uses Event and State enumeration to track the lifecycle status
Lifecycle owner provides lifecycle status to lifecycle aware components
LifecycleObserver registers the lifecyclestatus to respond and perform actions

LifeCycleActivity.kt
class LifeCycleActivity : AppCompatActivity() {

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

Log.i(TAG, "LifecycleOwner ON_CREATE")

lifecycle.addObserver(LifeCycleActivityObserver())
}

companion object {
private val TAG : String = LifeCycleActivity::class.java.simpleName
}
}
LifeCycleActivityObserver.kt
class LifeCycleActivityObserver : LifecycleObserver {

companion object {
private val TAG : String = LifeCycleActivityObserver::class.java.simpleName
}

@OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
fun onCreateEvent() {
Log.i(TAG, "Observer ON_CREATE")
}
}
While running the application, initially the LifeCycleOwners events will get called and then the LifeCycleObservers events will be triggered but when the activity gets either paused or gets destroyed then the LifeCycleObservers events will get triggered and then the LifeCycleOwners lifecycle events will get triggered.



Navigation
Handles everything needed for in-app navigation
Simplifies implementiing navigation  for users in the app
Allows user to move across, into and backout

Principles of Navigation :
  • Fixed start destination - This is the screen which the user sees when they launch the application from the launcher and also it has to be the last screen which user sees before returning to the launcher by pressing the back button
  • Navigation state should be  represented via stack of destinations - The navigation stack should have the start destination of the app at the bottom of the stack and the current destination at the top
  • The up button should never exit the app - if the user is at start destination of the application, the up button should not be shown
  • Up and back are identical within app's task - When we are on our own task in the application, the up and back button should have same functionality
Why Navigation Components?
  • Handles Fragment Transaction
  • Handles up and back actions correctly. Need not be handled manually
  • Includes Navigation UI Patterns such as Bottom Navigation, Navigation Drawers with minimal boilerplate code
  • Provides type safety when passing information -  we use safeargs while using navigation components which provides type safety assess to the arguments
  • Visualizing and editing navigation from Android studio Navigation editor
  • Provides standardized resources for animations and transitions
Navigation component consists of 3 key parts working together in Harmony :
Navigation Graph - Contains all the navigation related information in a centralized location
NavHostFragment - Special widget to add in the layout(Eg MainActivity)
NavController - Kotlin or Java object that keeps track of current position within the navigation graph

private fun setUpBottomNav(navController: NavController) {
bottom_nav?.let {
NavigationUI.setupWithNavController(it, navController)
}
}

private fun setUpSideNav(navController: NavController) {
nav_view?.let {
NavigationUI.setupWithNavController(it, navController)
}
}

private fun setUpActionBar(navController: NavController) {
NavigationUI.setupActionBarWithNavController(this, navController, drawer_layout)
}

override fun onCreateOptionsMenu(menu: Menu?): Boolean {
menuInflater.inflate(R.menu.toolbar_menu, menu)
return true
}

override fun onOptionsItemSelected(item: MenuItem): Boolean {
val navController : NavController = Navigation.findNavController(this, R.id.nav_host_fragment)
val navigated : Boolean = NavigationUI.onNavDestinationSelected(item!!, navController)
return navigated || super.onOptionsItemSelected(item)
}

override fun onSupportNavigateUp(): Boolean {
return NavigationUI.navigateUp(
Navigation.findNavController(this, R.id.nav_host_fragment), drawer_layout)
}
Actions : 
Navigation actions are connection between Destinations.

SafeArgs : 
Is a gradle plugin
It generates simple object and builder class for the type safe access to arguements for passing between destination and actions.

Class + Directions -> for sending
Class + Args -> for recieving



To add a navigation component, we need to go to the res folder and add Android Resource File and add a file and give a name navigation.xml and select Resource Type as Navigation from the dropdown. This creates a navigation folder inside res folder and the navigation.xml will be available within it. Add the fragments and make the navigation links as required for the flow between the fragments. If we need to make any fragment as HomeFragment, then we just need to select the fragment and click on the home option from the top menu.
Now in navigation.xml file, if  <fragment> doesn't have the property tools:layouit="@layout/fragment_home" then we need to add them manually.
In activity_main.xml, we need to add the following in the fragments tab
<fragment
android:layout_width="match_parent"
android:layout_height="match_parent"
android:name="androidx.navigation.fragment.NavHostFragment"
android:id="@+id/navControllerFragment"
app:defaultNavHost="true"
app:navGraph="@navigation/navigation" />





Comments

Popular posts from this blog

Android - Using KeyStore to encrypt and decrypt the data

Stack and Queue

Java Reflection API