Architecture Components in Android

ViewBinding

View binding is a feature that allows you to more easily write code that interacts with views. Once view binding is enabled in a module, it generates a binding class for each XML layout file present in that module.
In most cases view binding replaces findViewById
  • Setup instructions
    • View binding is enabled on a module by module basis. 
    • To enable view binding add the following code in build.gradle (Module) file.
 buildFeatures {
viewBinding true
}
    • If you want a layout file to be ignored while generating binding classes, add the following attribute to the root view of that layout file. 
 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/RelativeLayout01"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:viewBindingIgnore="true">

</RelativeLayout>
  • Usage
    • Each binding class contains references to the root view and all views that have an ID. 
    • Use view binding in activities
      • Perform the following task in the activity's onCreate() callback method:
        • Call the static inflate() method included in the generated binding class.
        • Get a reference to the root view by calling the getRoot()
        • Pass the root view to setContentView() method
    • Use view binding in fragments
      • Perform the following task in the fragment's onCreateView() callback method:
        • Call the static inflate() method included in the generated binding class.
        • Get a reference to the root view by calling the getRoot()
        • Return the root view from onCreateView() method
    • Provide hinds for different configurations
      • For implementation click the link of the header.
  • Differences from findViewById
    • View binding has important advantages over using findViewById
      • Null safety
        • Since view binding creates direct references to views, there's no risk of a null pointer exception due to an invalid view ID.
      • Type safety
        • The fields in each binding class have types matching the views they reference in the XML file. This means that there's no risk of a class cast exception.
  • Comparision with data binding
    • View binding and data binding both create binding classes that you can use to reference views directly. However, view binding is intended to handle simpler use cases and provides the following benefits over data binding.
      • Faster compilation: View binding requires no annotation processing, so compile times are faster.
      • Ease of use: View binding does not require specially-tagged XML layout files, so it is faster to adopt in your applications.
    • View binding has following limitations:
      • View binding doesn't support two-way data binding.
      • View binding doesn't support layout variables or layout expressions, so it can't be used to declare dynamic UI content straight from XML layout files.
    • So it is better to use view binding and data binding in a project.

Migrate from Kotlin Synthetics to Jetpack view binding.

Kotlin Android Extensions is deprecated. To migrate from Kotlin synthetics to view binding, please click the header.

Data Binding Library

The Data Binding Library is a support library that allows you to bind UI components in your layouts to data sources in your app using a declarative format rather than programmatically.
Data Binding Library removes findViewById()
Binding components in the layout file lets you remove many UI framework calls in your activities making them simpler and easier to maintain. This can also improve your application's performance and help prevent memory leaks and null pointer exceptions.
  • Using the Data Binding Library
    • Layouts an binding expressions
      • The expression language allows you to write expressions that connect variables to the views in the layout.
      • The Data Binding Library automatically generates the classes required to bind the views in the layout with your data objects.
      • The library provides features such as imports, variables and includes that you can use in your layouts.
    • Work with observable data objects
      • The Data Binding Library provides classes and methods to easily observe data for changes. 
      • You can make your variables or their properties observable. The library allows you to make objects, fields, or collections observable.
    • Generated binding classes
      • The Data Binding Library generates binding classes that are used to access the layout's variables and views. 
    • Binding adapters
      • For every layout expression, there is a binding adapter that makes the framework calls required to set the corresponding properties or listeners.
    • Bind layout views to Architecture Components
      • You can use the Architecture Components with Data Binding Library to further simplify the development of your UI.
      • E.g. the binding adapter can take care of calling the setText() method to set the text property or call the setOnClickListener() method to add a listener to the click event.
    • Two-way data binding
      • The Data Binding Library support two-way data binding.
  • Build environment
    • To configure your app to use data binding, enable the dataBinding build uption in your build.gradle (Module) file.
  • Android Studio support for data binding
    • Data Binding Library supports the following features
      • Syntax highlighting
      • Flagging of expression language syntax errors.
      • XML code completion.
      • References including navigation
    • If you need to display a default value only during the design phase of your project, you can use tools attributes instead of default expression values as described in Tools Attributes Reference.

Layouts and binding expressions

The Data Binding Library automatically generates the classes required to bind the views in the layout with your data objects.
Data binding layout files are slightly different and start with a root tag of layout followed by a data element and a view root element. For e.g. use the link of the header.
Note: Layout expressions should be kept small and simple, as they can't be unit tested and have limited IDE support. In order to simplify layout expressions, you can use custom binding adapters.
  • Data object
    • For implementation use the link of the header.
  • Binding data
    • A binding class is generated for each layout file. 
    • The name of the class is based on the name of the layout file, converting it to Pascal case and adding the Binding suffix to it.
    • The class holds all the bindings from the layout properties to the layout's views and knows how to assign values for the binding expressions.
    • For implementation use the link of the header.
  • Expression language
    • For implementation use the link of the header.
    • Null coalescing operator
      • The null coalescing operator (??) chooses the left operand if isn't null or the right operand if the former is null.
  • Event handling
    • Data binding allows you to write expression handling events that are dispatched from the views.
    • For implementation use the link of the header.
    • You can use the following mechanisms to handle an event.
      • Method references.
        • Events can be bound to handler methods directly (android:onClick).
        • You will get a compile time error if the method doesn't exist or its signature is incorrect.
        • The actual listener implementation is created when the data is bound unlike listener bindings the actual listener implementation is created when the event is triggered.
        • The parameters of the method must match the parameters of the event listener.
        • For implementation click the link of the header.
      • Listener bindings
        • The actual listener implementation is created when the event is triggered unlike method references the actual listener implementation is created when the data is bound.
        • Only your return value must match the expected return value of the listener (unless it is expecting void)
        • For implementation click the link of the header.
        • Avoid complex listeners
          • Listeners containing complex expressions make your layouts hard to read and maintain.
          • These expressions should be as simple as passing available data from your UUI to your callback method.
  • Imports, variables and includes
    • Imports
      • Imports allow you to easily reference classes inside your layout file. 
      • Zero or more import elements may be used inside the data element.
      • For implementation use the link of the header.
      • Type aliases
        • When there are class name conflicts, one of the classes may be renamed to an alias.
      • Import other classes
        • Imported types can be used as type references in variables and expressions.
        • You can also use the imported types to cast part of an expression.
    • Variables
      • You can use multiple variable elements inside the data element. 
      • Each variable element describes a property that may be set on the layout to be used in binding expressions within the layout file. For implementation click the link of the header.
    • Includes
      • Variables may be passed into an included layout's binding from the containing layout by using the app namespace and the variable name in an attribute. For implementation click the link of the header.
      • Data binding doesn't support include as a direct child of a merge element.

Work with observable data objects

Generated binding classes

Binding adapters

Bind layout views to Architecture Components

Two-way data binding

Handling Lifecycles with Lifecycle-Aware Components

Lifecycle-aware components perform actions in response to a change in the lifecycle status of another component, such as activities and fragments.
By using lifecycle-aware components, you can move the code of dependent components out of the lifecycle methods and into the components themselves.
To download the dependencies please click androidx.lifecycle
  • Lifecycle
    • Lifecycle is a class that holds the information about the lifecycle state of a component (activity or fragment) and allows other objects to observe this state.
    • Lifecycle uses two main enumerations to track the lifecycle status for its associated component
      • Event
        • The lifecycle events that are dispatched from the framework and the Lifecycle class. These events map to the callback events in activities and fragments.
      • State
        • The current state of the component tracked by the Lifecycle object.
    • A class can monitor the component's lifecycle status by implementing DefaultLifecycleObserver and overriding corresponding methods such as onCreate, onStart, etc.
    • Then you can add an observer by calling the addObserver() method of the Lifecycle class and passing an instance of your observer.
  • LifecycleOwner
    • LifecycleOwner is a single method interface that denotes that the class has a Lifecycle.
    • It has one method, getLifecycle() which must be implemented by the class.
    • If you are trying to manage the lifecycle of a whole application process use ProcessLifeCycleOwner.
    • Any custom application class can implement the LifecycleOwner interface.
    • Components that implement DefaultLifecycleObserver work seamlessly with components that implement LifecycleOwner because an owner can provide a lifecycle, which an observer can register to watch.
    • If a library provides classes that need to work with Android lifecycle, we recommend that you use lifecycle-aware components.
    • To implement a custom LifecycleOwner please follow the link.
  • Best practices for lifecycle-aware components
    • Keep your UI controllers (activities and fragments) as lean as possible. They should not try to acquire their own data; instead, use a ViewModel to do that and observer a LiveData object to reflect the changes back to the views.
    • Try to write data-driven UIs where your UI controller's responsibility is to update the views as data changes, or notify user actions back to the ViewModel.
    • Put your data logic in your ViewModel class. ViewModel should server as the connector between your UI controller and the rest of your app.
    • Use Data Binding to maintain a clean interface between your views and the UI controller. If you are using Java then use Butter Knife.
    • If your UI is complex, consider creating a presenter class to handle UI modifications.
    • Avoid referencing a View or Activity context in your ViewModel.
    • Use Kotlin coroutines to manage long-running tasks and other operations that can run asynchronously. 
  • Use cases for lifecycle-aware components
    • To know more follow the header link.
  • Handling on stop events
    • To know more follow the header link.
  • Additional resources

ViewModel Overview

The ViewModel class is designed to store and manage UI-related data in a lifecycle conscious way. It's a class that is responsible for preparing and managing the data for an Activity or a Fragment. The ViewModel class allows data to survive configuration changes such as screen rotations. 
ViewModel is always created in association with a scope (a fragment or an activity) and will be retained as long as the scope is alive i.e. if it is an Activity, until it is finished() is called or destroyed. ViewModel will not be destroyed if it's owner is destroyed for a configuration change (e.g. rotation). The new owner instance just re-connects to the existing model.
ViewModel usually expose the information via LiveData or Android Data Binding.

LiveData Overview

LiveData is an observable data holder class. LiveData is lifecycle aware, meaning it respects the lifecycle of other app components such as activities, fragments or services. This awareness ensures LiveData only updates app component observers that are in an active lifecycle state.
LiveData considers an observer, which is represented by the Observer class, to be in an active state if its lifecycle is in the STARTED or RESUMED state. LiveData only notifies active observers about updates. Inactive observers registered to watch LiveData objects aren't notified about changes. Activties and Fragments are instantly unsubscribed when their lifecycles are destroyed.

Save UI states

To bridge the gap between user expectation and system behavior, use a combination of ViewModel objects, the onSaveInstanceState() method, and/or local storage to persist the UI state across such application and activity instance transitions.
  • User expectations and system behavior
    • Depending upon the action a user takes, they either expect that activity state to be cleared for the state to be preserved.
    • In some cases the system automatically does what is expected by the user. In other cases the system does the opposite of what the user expects.
    • User-initiated UI state dismissal
      • The user can completely dismiss an activity by
        • pressing the back button
        • swiping the activity off of the Recent Screen
        • navigating up from the activity
        • killing the app from Settings screen
        • completing some sort of "finishing" action (which is backed by Activity.finish())
      • When a user presses back button from an activity then the expectation of the user meets with the behavior of the system i.e. the system destroys the activity along with its state.
    • System-initiated UI state dismissal
      • If your application is placed in the background the system does its best to keep your application process in memory. However, the system may destroy the application process while the user is away interacting with other applications. In such a case, the activity instance is destroyed, along with any state stored in it. When the user relaunches the application, the activity is unexpectedly in a clean state.
      • To know more click the header.
  • Options for preserving UI state
    • Please refer the table given in the header link
  • Use ViewModel to handle configuration changes
    • ViewModel is ideal for storing and managing UI-related data while the user is actively using the application.
    • To implement a ViewModel, see the ViewModel guide
    • A ViewModel is associated with an activity (or some other lifecycle) - it stays in memory during a configuration change and the system automatically associates the ViewModel with the new activity instance that results from the configuration change.
    • ViewModels are automatically destroyed by the system when your user backs out of your activity or fragment or if you call finish().
  • Use onSaveInstanceState() as backup to handle system-initiated process death
    • The onSaveInstanceState() callback stores data needed to reload the state of a UI controller, such as an activity or fragment, if the system destroys and later recreates that controller.
    • onSavedInstanceState() serializes data to disk. Serialization can consume a lot of memory if the objects being serialized are complicated. Because this process happens on the main thread during a configuration change, long-running serialization can cause dropped frames and visual stutter.
    • Do not use onSavedInstanceState() to store large amounts of data, such as bitmaps, nor complex data structures that require lengthy serialization or deserialization. Instead, store only primitive types and simple, small objects such as String.
    • If a piece of UI state data, such as search query, were passed in as an intent extra when the activity was launched, you could use the extras bundle instead of the onSavedInstanceState().
  • Hook into saved state using SavedStateRegistry
    • UI controllers, such as an Activity or Fragment, implement SavedStateRegistryOwner and provide a SavedStateRegistry that is bound to that controller. 
    • SavedStateRegistry allows components to hook into your UI controller's saved state to consume or contribute to it.
    • You can retrieve SavedStateRegistry from within your UI controller by calling getSavedStateRegistry()
    • Components that contribute to saved state must implement SavedStateRegistry.SavedStateProvider, which defines a single method called saveState().
    • The saveState() method allows your component to return a Bundle containing any state that should be saved from that component.
    • SavedStateRegistry calls this method during the saving state phase of the UI controller's lifecycle.
    • To register a SavedStateProvider, call registerSavedStateProvider() on the SavedStateRegistry, passing a key to associate with the provider's data as well as the provider.
    • The previously saved data for the provider can be retrieved from the saved data by calling consumeRestoredStateForKey() on the SavedStateRegistry, passing in the key associated with the provider's data.
    • Within an Activity or Fragment, you can register a SavedStateProvider in onCreate() after calling super.onCreate().
    • Alternatively, you can set a LifeCycleObserver on a SavedStateRegistryOwner, which implements LifeCycleOwner, and register the SavedStateProvider once the ON_CREATE event occurs.
    • By using the LifeCycleObserver, you can decouple the registration and retrieval of the previously saved state from the SavedStateRegistryOwner itself.
    • To know the implementation use the link of the header.
  • Use local persistence to handle process death for complex or large data
    • Persistent local storage, such as a database or shared preferences, will survive for as long as your application is installed on the user's device (unless the user clears the data for your app). 
    • Neither ViewModel nor saved instance state are long-term storage solutions and thus are not replacements for local storage, such as database. Instead you should these mechanisms for temporarily storing transient UI state only and use persistent storage for other app data.
  • Managing UI state: divide and conquer
    • You can efficiently save and restore UI state by dividing the work among the various types of persistence mechanisms.
      • Local persistence: Stores all data you don't want to lose if you open and close the activity.
      • ViewModel: Stores in memory all the data needed to display the associated UI controller.
      • onSaveInstanceState(): Stores a small amount of data needed to easily reload activity state if the system stops and then recreates the UI controller.
      • For implementing this mechanism for the link of the header.
  • Restoring complex states: reassembling the pieces
    • When it is time for the user to return to the activity, there are two possible scenarios for recreating the activity:
      • The activity is recreated after having been stopped by the system. The activity has the query saved in an onSaveInstanceState() bundle, and should pass the query to the ViewModel. The ViewModel sees that it has no search results cached and delegates loading the search results using the given search query.
      • The activity is created after a configuration change. The activity has the query saved in an onSaveInstanceState() bundle and the ViewModel already has the search results cached. You pass the query from the onSaveInstanceState() bundle to the ViewModel, which determines that it already has loaded the necessary data and that it does not need to re-query the database.

Saved State module for ViewModel

ViewModel objects can handle configuration changes, so you don't need to worry about state in rotations or other cases. However, if you need to handle system-initiated process death, you might want to use onSaveInstanceState() method as backup.
UI state is usually stored or reference in ViewModel objects and not activities, so using onSaveInstanceState() requires some boilerplate that the saved state module can handle for you. 
When using this module (saved state module), ViewModel objects receive a SavedStateHandle object through its constructor. This object is a key-value map that lets you write and retrieve objects to and from the saved state. These values persist after the process is killed by the system and remain available through the same object.
Note: State must be simple and lightweight. For complex or large data, you should use local persistence.

Use Kotlin coroutines with lifecycle-aware components

Kotlin coroutines provide an API that enables you to write asynchronous code. With Kotlin coroutines, you can define a CoroutineScope which helps you to manage when your coroutines should run. Each asynchronous operation runs within a particular scope.
Lifecycle-aware components provide first-class support for coroutines for logical scopes in your app along with an interoperability layer with LiveData.

Paging library Overview

The Paging library helps you load and display pages of data from a larger dataset from local storage or over network. 
  • Benefits of using the Paging library
    • In-memory caching for your paged data. This ensures that your application uses system resources efficiently while working with paged data.
    • Built-in request deduplication, ensuring that your application uses network bandwidth and system resources efficiently.
    • Configurable RecyclerView adapters that automatically request data as the user scrolls toward the end of the loaded data.
    • First-class support for Kotlin coroutines and Flow, as well as LiveData and RxJava.
    • Built-in support for error handling, including refresh and retry capabilities.
  • Setup
    • Click the header to download the required dependencies in your build.gradle (Module) file.
  • Library architecture
    • The library's components operate in three layers of your application.
      • The Repository layer
        • The primary Paging library component in the repository layer is PagingSource. Each PagingSouce object defines a source of data and how to retrieve data from that source. A PagingSource object can load data from any single source, including network sources and local databases.
        • Another Paging library component that you might use is RemoteMediator. A RemoteMediator object handles paging from a layered data source, such as netword data source with a local database cache.
      • The ViewModel layer
        • The Pager component provides a public API for constructing instances of PagingData that are exposed in reactive streams, based on a PagingSource object and a PagingConfig configuration object.
        • The component that connects the ViewModel layer to the UI is PagingData. A PagingData object is a container for a snapshot of paginated data. It queries a PagingSource object and stores the result.
      • The UI layer
        • The primary Paging library component in the UI layer is PagingDataAdapter, a RecyclerView adapter that handles paginated data.
        • Alternatively, you can use the included AsyncPagingDataDiffer component to build your own custom adapter.

Load and display paged data

  • Define a data source
    • The first step is to define a PagingSource implementation to identify the data source.
    • The PagingSource API class includes the load() method, which you must override to indicate how to retrieve paged data from the corresponding data source.
    • Use the PagingSource class directly to use Kotlin coroutines for async loading.

Do's and Don'ts

  • ViewModel should never access your view hierarchy or hold a reference back to the Activity or Fragment.
  • Avoid referencing a View or Activity context in your ViewModel.
  • Use LiveData to communicate between the lifecycle owners and other objects with a different lifespan, such as ViewModel objects.
  • Do not use onSavedInstanceState() to store large amounts of data, such as bitmaps, nor complex data structures that require lengthy serialization or deserialization. Instead, store only primitive types and simple, small objects such as String.
  • It is better to use view binding and data binding in a project.
  • If you need to display a default value only during the design phase of your project, you can use tools attributes instead of default expression values as described in Tools Attributes Reference.

Reference


 

Comments

Popular posts from this blog

DataBinding in Android

SSLSocketFactory in Android