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.
A 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.
- Implement a ViewModel
- Architecture Components provides ViewModel helper class for the UI controller that is responsible for preparing data for the UI.
- A ViewModel must never reference a view, Lifecycle, or any class that may hold a reference to the activity context.
- ViewModel objects can contain LifeCycleObservers such as LiveData objects.
- If the ViewModel needs the Application context, for example to find a system service, it can extend the AndroidViewModel class and have a constructor that received Application in the constructor, since Application class extends Context.
- The lifecycle of a ViewModel
- ViewModel objects are scoped to the LifeCycle passed to the ViewModelProvider while getting the ViewModel.
- The ViewModel remains in memory until the LifeCycle it's scoped (activity or fragment) to, goes away permanently: in the case of an activity, when it finishes, while in the case of a fragment when it's detached.
- You usually request a ViewModel the first time the system calls an activity object's onCreate() method. The system call onCreate() several times throughout the life of an activity, such as when a device screen is rotated. The ViewModel exists from when you first request a ViewModel until the activity is finished and destroyed.
- Share data between fragments
- Fragments can share a ViewModel using their activity scope to handle their communication.
- To know more about it click the header.
- Replacing Loaders with ViewModel
- You can use ViewModel, with a few other classes (Room and LiveData) to replace the loader you are using.
- Using a ViewModel separates your UI controller from the data-loading operation, which means you have fewer strong references between classes.
- Use coroutines with ViewModel
- ViewModel includes support for Kotlin coroutines.
- Click Use Kotlin coroutines with Android Architecture Components
- Further Information
- As your data grows more complex, you might choose to have a separate class just to load the data.
- The purpose of ViewModel is to encapsulate the date for a UI controller to let the data survive configuration changes.
- Additional resources
- To learn more click the header.
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.
- The advantages of LiveData
- Ensures your UI matches your data state
- No memory leaks
- No crashes due to stopped activities
- No more manual lifecycle handling
- Always up to date data
- Proper configuration changes
- Sharing resources
- Work with LiveData objects
- Follow these steps
- Create an instance of LiveData to hold a certain type of data. This is usually done within your ViewModel class.
- Create an Observer object that defines the onChanged() method, which controls what happens when the LiveData object's held data changes. You usually create an Observer object in a UI controller.
- Attach the Observer object to the LiveData object using the observe() method. The observe() method takes a LifeCycleOwner object. This subscribes the Observer object to the LiveData object so that it is notified of changes. You usually attach the Observer object in a UI controller.
- When you update the value stored in the LiveData object, it triggers all registered observers as long as the attached LifeCycleOwner is in the active state (STARTED or RESUMED).
- LiveData allows UI controller observers to subscribe to updates. When the data held by the LiveData object changes, the UI automatically updates in response.
- You can register an observer without an associated LifeCycleOwner object using the observerForever(Observer) method. In this case, the observer is considered to be always active and is therefore always notified about modifications. You can remove these observers call the removeObserver(Observer) method.
- Create LiveData objects
- LiveData is a wrapper that can be used with any data, including objects that implement Collections (List).
- A LiveData object is usually stored within a ViewModel object and is accessed via a getter method.
- Observe LiveData objects
- An app component's onCreate() method is the right place to begin observing a LiveData object for the following reasons.
- To ensure the system doesn't make redundant calls from an activity or fragment's onResume() method.
- To ensure that the activity or fragment has data that it can display as soon as it becomes active.
- LiveData delivers updates only when data changes, and only to active observers.
- Observers also receive an update when they change from an inactive to active state. If the observer changes from inactive to active state a second time, it only receives an update if the value has changed since the last time it became active.
- Update LiveData objects
- LiveData has no public methods to update the stored data.
- The MutableLiveData class has two public methods: setValue(T) and postValue(T). You must use these if you need to edit the value stored in a LiveData object.
- You must call the setValue(T) method to update the LiveData object from the main thread. If the code is executed in a worker thread, you can use the postValue(T) method instead to update the LiveData object.
- MutableLiveData is used in the ViewModel and then the ViewModel provides immutable LiveData objects to the observers.
- Use LiveData with Room
- Room generates all the necessary code to update the LiveData object when a database is updated.
- The generated code runs the query asynchronously on a background thread when needed.
- Use coroutines with LiveData
- LiveData includes support for Kotlin coroutines.
- Please refer the article: Use Kotling coroutines with Android Architecture Components.
- LiveData in an app's architecture
- Use LiveData to communicate between the lifecycle owners and other objects with a different lifespan, such as ViewModel objects.
- Create LiveData objects in the ViewModel and use them to expose state to the UI layer.
- Activities and fragments should not hold LiveData instances because their role is to display data, not hold state.
- LiveData is not designed to handle asynchronous steams of data. For this you can use MediatorLiveData but it has limitations.
- If you need to use streams of data in other layers of your application, consider using Kotlin Flows and then converting them to LiveData in the ViewModel using asLiveData().
- Extend LiveData
- LiveData considers an observer to be in an active state i.e. the observer's lifecycle is in either the STARTED or RESUMED states.
- Methods in LiveData
- onActive() - This method is called when the LiveData object has an active observer.
- onInactive() - This method is called when the LiveData object doesn't have any active observers.
- setValue(T) - This method updates the value of the LiveData instances and notifies any active observers about the change.
- postValue(T) - This method updates the value of the LiveData instances and notifies any active observers about the change.
- If the LifeCycle object is not in an active state, then the observer isn't called even if the value changes.
- After the LifeCycle object is destroyed, the observer is automatically removed.
- LiveData objects are lifecycle-aware means that you can share them between multiple activities, fragments and services.
- You can implement the LiveData class as a singleton. The example is given in the documentation.
- LiveData only connects to the system service if one or more of them is visible and active.
- Transform LiveData
- The Lifecycle package provides the Transformations class which includes helper methods to make changes to the value stored in a LiveData object before dispatching it to the observers.
- Transformations.map() - Applies a function on the value store in the LiveData object, and propagates the result downstream.
- Transformations.swithMap() - Similar to map(), applies a function to the value stored in the LiveData object and unwraps and dispatches the result downstream.
- You can use transformations methods to carry information across the observer's lifecycle.
- If you think you need a Lifecycle object inside a ViewModel object, a transformation is probably a better solution.
- To implement your own transformation you can use the MediatorLiveData class, which listens to other LiveData objects and processes events emitted by them. MediatorLiveData correctly propagates its state to the source LiveData object.
- Merge multiple LiveData sources
- MediatorLiveData is a subclass of LiveData that allows you to merge multiple LiveData sources.
- Your activity only needs to observe the MediatorLiveData object to receive updates from both sources.
- For a detailed example, see the Addendum: exposing network status.
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.
- Setup
- You can accept a SavedStateHandle as a constructor argument to your ViewModel.
- You can then retrieve an instance of your ViewModel without any additional configuration. The default ViewModel factory provides the appropriate SavedStateHandle to your ViewModel.
- When providing a custom ViewModelProvider.Factory instance, you can enable usage of SavedStateHandle by extending AbstractSavedStateViewModelFactory.
- When using an earlier version of the fragments library, follow the instructions for declaring dependencies in the Lifecycle release notes to add a dependency on lifecycle-viewmodel-savedstate and use SavedStateViewModelFactory as your factory.
- For implementation please use the link of the header.
- Working with SavedStateHandle
- The SavedStateHandle class is a key-value map that allows you to write and retrieve data to and from the saved state through the set() and get() methods.
- Additionally, you can retrieve values from SavedStateHandle that are wrapped in a LiveData observable using getLiveData().
- When the key's value is updated, the LiveData recieves the new value. This updated value can be used to transform LiveData.
- By using SavedStateHandle, the query value is retained across process death, ensuring that the user sees the same set of filtered data before and after recreation without the activity or fragment needing to manually save, restore and forward that value back to the ViewModel.
- SavedStateHandle also has other methods
- Supported types
- Data kept within a SavedStateHandle is saved and restored as a Bundle, along with the rest of the savedInstanceState for the activity or fragment.
- To know more please click the link of the header.
- Saving non-parcelable classes
- If a class does not implement Parcelable or Serializable and cannot be modified to implement one of those interfaces, then it is not possible to directly save an instance of that class into a SavedStateHandle.
- SavedStateHandle allows you to save any object by providing your own logic for saving and restoring your object as a Bundle using the setSavedStateProvider() method.
- SavedStateRegistry.SavedStateProvider is an interface that defines a single saveState() method that returns a Bundle containing the state you want to save.
- When SavedStateHandle is ready to save its state, it calls saveState() to retrieve the Bundle from SavedStateProvider and saves the Bundle for the associated key.
- To implement this mechanism please click the link of the header.
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.
Comments
Post a Comment