MVVM , ViewModel , LiveData

The MVVM (Model-View-ViewModel) architecture is a design pattern used in software development, particularly in building user interfaces. It provides a clear separation of concerns by dividing the codebase into three main components: the Model, the View, and the ViewModel. MVVM aims to improve code organization, maintainability, and testability of applications.

MVVM is a powerful architecture for building robust and scalable applications. It promotes separation of concerns, testability, and maintainability, making it easier to develop, test, and maintain complex UI-driven applications.

No alt text provided for this image

Above image is copied from internet.

MVVM

  • Overview: MVVM is a data-driven architectural pattern that leverages data binding to establish a connection between the View and ViewModel.
  • Key Concepts: Model represents data and business logic, View displays the UI, and ViewModel exposes data and commands to the View.
  • Benefits: Declarative UI programming, separation of concerns, reduced boilerplate code.

Use Cases: Data-driven applications, platforms supporting data binding.MVVM, the architecture consists of three main components:


1.View (UI):?

  • The View represents the user interface or the visual representation of the application. It includes the activities, fragments, or custom views, XML layout files, UI controls, and the logic for rendering data and capturing user input.
  • It is responsible for displaying data to the user and capturing user input. The view interacts with the ViewModel through data binding.
  • In MVVM, the View should be as dumb as possible, focusing only on displaying data and capturing user actions. It should not contain any business logic or data manipulation.

2. ViewModel:

  • The ViewModel acts as a mediator between the View and the Model. It exposes data and operations that the View can bind to and interact with. It contains the business logic and data required by the view. The ViewModel retrieves data from the Model and prepares it for presentation in the View.
  • The ViewModel provides data in a format that is convenient for the View, often in the form of observable properties or LiveData objects. It also contains methods for handling user actions or events triggered by the View.
  • The ViewModel is responsible for maintaining the state of the View and coordinating with the Model to fetch or update data. It does not have direct knowledge of the View, ensuring separation of concerns.

3. Model (Data Layer):

  • The Model represents the data and business logic of the application. It encapsulates the data sources, such as databases, network clients,web services, or repositories, and provides methods to perform operations on the data.
  • The Model component is responsible for fetching, updating, and persisting data. It should ideally be independent of the user interface or the View layer.

+-----------------+
|? ? View (UI)? ? |
+-----------------+
|? ?Data Binding? |
|? ? (XML Layout) |
+--------+--------+
? ? ? ? ?|
? ? ? ? ?|? ? ?+------------------+
? ? ? ? ?+---->|? ViewModel? ? ? ?|
? ? ? ? ? ? ? ?|? ? ? ? ? ? ? ? ? |
? ? ? ? ? ? ? ?|? Business Logic? |
? ? ? ? ? ? ? ?|? ? ? ? ? ? ? ? ? |
? ? ? ? ? ? ? ?+--------+---------+
? ? ? ? ? ? ? ? ? ? ? ? |
? ? ? ? ? ? ? ? ? ? ? ? |? ? ?+-------------+
? ? ? ? ? ? ? ? ? ? ? ? +---->|? ?Model? ? ?|
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? | (Data Layer) |
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? +-------------+
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? |? ?Services? |
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? |? ?Database? |
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? |? ?Network? ?|
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? +-------------+        

In the above diagram:

  • The Model represents the data and business logic layer of the application.
  • The View represents the user interface layer responsible for rendering the UI and capturing user actions.
  • The ViewModel acts as a bridge between the View and the Model. It retrieves data from the Model, prepares it for presentation in the View, and handles user actions.
  • Data flows from the Model to the ViewModel and then to the View for display.
  • User actions, such as button clicks or input events, are captured by the View and passed to the ViewModel for processing.
  • The ViewModel communicates with the Model to fetch or update data, and it exposes data properties that the View can bind to for automatic UI updates.
  • Data binding connects the ViewModel properties to the View, allowing seamless synchronization of data between them.

The key aspect of MVVM is the data binding between the view and the ViewModel. The view binds its UI elements directly to the ViewModel properties, eliminating the need for manual UI updates. When the ViewModel properties change, the view automatically reflects the updates, and user interactions trigger actions in the ViewModel.

The above block diagram illustrates the flow of data and actions in the MVVM architecture, emphasizing the separation of concerns and the interaction between the components. It helps to visualize how the View, ViewModel, and Model interact with each other in an MVVM-based application.

Please note that MVVM uses a one-to-many relationship between the View and the ViewModel. i.e one view can have multiple viewModels .


Key principles and features of the MVVM architecture:

  • Data Binding: MVVM encourages the use of data binding techniques to establish a connection between the View and the ViewModel. This allows for automatic updates of the UI when the underlying data changes.
  • Two-Way Data Binding: MVVM supports two-way data binding, where changes in the UI are propagated back to the ViewModel, enabling seamless synchronization between the View and the ViewModel.
  • Dependency Injection: MVVM can benefit from dependency injection to provide necessary dependencies to the ViewModel, such as repositories or services.
  • Testing: MVVM promotes testability by separating the business logic in the ViewModel from the UI components. Unit testing the ViewModel becomes easier as it can be tested independently of the View, using mock data sources.
  • Separation of Concerns: MVVM enforces a clear separation of concerns by assigning specific responsibilities to each component. This improves code organization, maintainability, and modularity.


Limitation of MVVM:

  1. Learning Curve: MVVM introduces a new architecture pattern and concepts that developers may need to learn and understand. It requires familiarity with data binding, view model creation, and managing data flow between components.
  2. Increased Complexity: Implementing MVVM can increase the complexity of the codebase. There are more components involved, such as view models, data binding, and observers, which can make the code harder to read and maintain.
  3. Potential Overuse of Observables: In MVVM, observables like LiveData or RxJava are often used to propagate data changes between the view model and the view. While this simplifies data binding, it can lead to overuse of observables and potentially impact performance and memory usage if not managed properly.
  4. Testability Challenges: Testing view models and views in MVVM can be more challenging compared to other architectures. The separation of concerns between the view and view model can require additional setup and mocking of dependencies to properly isolate and test each component.
  5. Data Binding Learning Curve: Data binding, a core part of MVVM, has its own learning curve and can introduce complexities, especially for developers who are new to it. Understanding data binding expressions, binding adapters, and two-way binding may take time and effort.
  6. Steeper Learning Curve for New Developers: If you are working on a team where some members are not familiar with MVVM, it can take additional time and effort for them to understand and adapt to the MVVM architecture.

Example:

  1. Model:

data class Task(
? ? val id: Int,
? ? val title: String,
? ? val description: String,
? ? val status: String
)

class TaskRepository {
? ? // Simulated data source or API calls

? ? fun getTasks(): List<Task> {
? ? ? ? // Fetch tasks from a database or API
? ? ? ? return listOf(
? ? ? ? ? ? Task(1, "Task 1", "Description 1", "Pending"),
? ? ? ? ? ? Task(2, "Task 2", "Description 2", "Completed"),
? ? ? ? ? ? Task(3, "Task 3", "Description 3", "Pending")
? ? ? ? )
? ? }

? ? fun addTask(task: Task) {
? ? ? ? // Add task to the database or API
? ? }

? ? fun updateTaskStatus(taskId: Int, status: String) {
? ? ? ? // Update task status in the database or API
? ? }
}        


2. ViewModel:

Create the viewmodel

class TaskViewModel(private val repository: TaskRepository) : ViewModel() {
? ? private val _tasks = MutableLiveData<List<Task>>()
? ? val tasks: LiveData<List<Task>> get() = _tasks

? ? init {
? ? ? ? loadTasks()
? ? }

? ? private fun loadTasks() {
? ? ? ? _tasks.value = repository.getTasks()
? ? }

? ? fun addTask(task: Task) {
? ? ? ? repository.addTask(task)
? ? ? ? loadTasks() // Refresh the task list
? ? }

? ? fun updateTaskStatus(taskId: Int, status: String) {
? ? ? ? repository.updateTaskStatus(taskId, status)
? ? ? ? loadTasks() // Refresh the task list
? ? }
}        

Create a factory class that implements the ViewModelProvider.Factory interface. This factory will be responsible for creating instances of your ViewModel. For example:

class TaskViewModelFactory(private val taskRepository: TaskRepository) : 
                                                ViewModelProvider.Factory {
? ? override fun <T : ViewModel?> create(modelClass: Class<T>): T {
? ? ? ? if (modelClass.isAssignableFrom(TaskViewModel::class.java)) {
? ? ? ? ? ? return TaskViewModel(taskRepository) as T
? ? ? ? }
? ? ? ? throw IllegalArgumentException("Unknown ViewModel class")
? ? }
}        

In the above example, the factory receives an instance of the TaskRepository, which is a dependency required by the TaskViewModel.


3.View (Activity):

class TaskActivity : AppCompatActivity() {
? ? private lateinit var taskViewModel: TaskViewModel

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

? ? ? ? val taskRepository = TaskRepository() // Create an instance of the TaskRepository

? ? ? ? val viewModelFactory = TaskViewModelFactory(taskRepository) // Create the ViewModel factory

? ? ? ? taskViewModel = ViewModelProvider(this, viewModelFactory).get(TaskViewModel::class.java)
? ? ? ? // Retrieve the ViewModel instance using the factory and ViewModelProvider

? ? ? ? // Observe the tasks LiveData and update the UI accordingly
? ? ? ? taskViewModel.tasks.observe(this, Observer { tasks ->
? ? ? ? ? ? // Update the UI with the list of tasks
? ? ? ? ? ? // For example, update a RecyclerView adapter
? ? ? ? })

? ? ? ? // Handle user actions (e.g., button clicks) and call the ViewModel methods
? ? ? ? addButton.setOnClickListener {
? ? ? ? ? ? val newTask = Task(4, "New Task", "New Task Description", "Pending")
? ? ? ? ? ? taskViewModel.addTask(newTask)
? ? ? ? }
? ? }
}        

In this example, the implementation follows the MVVM pattern with the Activity acting as the View. The ViewModel (TaskViewModel) handles the logic and provides data to the View. The View observes the LiveData property (tasks) exposed by the ViewModel and updates the UI accordingly.

Typical set of dependencies that are commonly used for implementing MVVM architecture in an Android project. Please note that these dependencies may vary depending on your specific project requirements:

dependencies {
? ? // ViewModel
? ? implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.0'
? ??
? ? // LiveData
? ? implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.4.0'
? ??
? ? // Data Binding
? ? implementation 'androidx.databinding:databinding-runtime:7.0.2'
? ??
? ? // Retrofit (for network operations)
? ? implementation 'com.squareup.retrofit2:retrofit:2.9.0'
? ? implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
? ??
? ? // Room (for local database operations)
? ? implementation 'androidx.room:room-runtime:2.4.0'
? ? kapt 'androidx.room:room-compiler:2.4.0'
? ??
? ? // Coroutines (for asynchronous programming)
? ? implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.1'
? ??
? ? // Other dependencies
? ? implementation 'androidx.appcompat:appcompat:1.4.0'
? ? implementation 'androidx.core:core-ktx:1.7.0'
}        

These dependencies include the following:

  • lifecycle-viewmodel-ktx and lifecycle-livedata-ktx: These dependencies provide the ViewModel and LiveData classes from the Android Architecture Components. They are essential for implementing the MVVM pattern and handling lifecycle-aware data updates.
  • databinding-runtime: This dependency enables data binding in your project, allowing you to bind UI elements directly to ViewModel properties.
  • retrofit and converter-gson: These dependencies are used for making network requests and parsing JSON responses using Retrofit library.
  • room-runtime and room-compiler: These dependencies are used for local data storage and database operations using Room persistence library.
  • kotlinx-coroutines-android: This dependency provides support for asynchronous programming using Kotlin coroutines.
  • appcompat and core-ktx: These dependencies provide support libraries and Kotlin extensions for Android app development.

You can add these dependencies to your project's build.gradle file under the dependencies block and sync your project to make them available for use in your code. Remember to use the appropriate versions based on your project requirements and compatibility.

Note: It's important to regularly check for updates to these dependencies and use the latest stable versions to benefit from bug fixes, performance improvements, and new features.


In the above code is all the components are tightly coupled !

To achieve loose coupling and adhere to the dependency inversion principle, you can introduce interfaces and have the classes depend on those interfaces instead of concrete implementations. Here's an updated version of the code considering your suggestions:

interface ITask {
? ? fun getTasks(): List<ITask>
? ? fun addTask(task: ITask)
}

class Task : ITask {
? ? // Task implementation
}

interface ITaskRepository {
? ? fun getAllTasks(): List<ITask>
? ? fun addTask(task: ITask)
}

class TaskRepository : ITaskRepository {
? ? override fun getAllTasks(): List<ITask> {
? ? ? ? // Retrieve tasks from data source
? ? ? ? return listOf()
? ? }

? ? override fun addTask(task: ITask) {
? ? ? ? // Add task to data source
? ? }
}

class TaskViewModel(private val taskRepository: ITaskRepository) : ViewModel() {
? ? private val _tasks = MutableLiveData<List<ITask>>()
? ? val tasks: LiveData<List<ITask>> = _tasks

? ? fun loadTasks() {
? ? ? ? val allTasks = taskRepository.getAllTasks()
? ? ? ? _tasks.value = allTasks
? ? }

? ? fun addTask(task: ITask) {
? ? ? ? taskRepository.addTask(task)
? ? ? ? loadTasks()
? ? }
}

class TaskActivity : AppCompatActivity() {
? ? private lateinit var taskViewModel: TaskViewModel

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

? ? ? ? // Create the TaskViewModel by injecting the concrete implementation of ITaskRepository
? ? ? ? val taskRepository = TaskRepository()
? ? ? ? taskViewModel = ViewModelProvider(this, TaskViewModelFactory(taskRepository)).get(TaskViewModel::class.java)

? ? ? ? // Observe the tasks LiveData and update the UI accordingly
? ? ? ? taskViewModel.tasks.observe(this, { tasks ->
? ? ? ? ? ? // Update the UI with the list of tasks
? ? ? ? })

? ? ? ? // Trigger the loading of tasks
? ? ? ? taskViewModel.loadTasks()
? ? }
}        

With these updates, the ITask interface represents a more abstract concept of a task, and both Task and the repository implementations work with ITask instances. The TaskViewModel now uses MutableLiveData<List<ITask>> to hold and observe the list of tasks, and the addTask() method accepts ITask as a parameter, allowing for more flexibility and loose coupling.


By introducing interfaces and coding against abstractions, you can achieve better testability and maintainability, as well as flexibility in swapping out different implementations of the interfaces as needed.


ViewModel in detail :

In Android development, the ViewModel is part of the Android Architecture Components and is designed to store and manage UI-related data in a lifecycle-aware manner. The ViewModel class is responsible for preparing and managing the data for the UI, while surviving configuration changes (such as screen rotations) and providing a clean separation between the UI and the underlying data.

Here are some key aspects and benefits of using ViewModel:

  1. Lifecycle Awareness: The ViewModel is lifecycle-aware, meaning it is aware of the lifecycle of the associated UI component (usually an Activity or Fragment). It automatically handles configuration changes and survives them without losing its state. It ensures that data persists across configuration changes, which helps prevent data loss and unnecessary data fetching.
  2. Separation of Concerns: ViewModel promotes the separation of concerns by providing a dedicated class for handling UI-related data. This allows you to keep your UI components (like Activities and Fragments) lightweight and focused on the presentation logic, while moving the data management responsibilities to the ViewModel.
  3. Business Logic and Data Management: ViewModel is an ideal place to handle business logic and data management operations. It can interact with repositories, network services, or databases to fetch and manipulate data. It encapsulates the data operations and exposes the necessary data to the UI components.
  4. Sharing Data between UI Components: ViewModel can act as a communication channel between different UI components, such as Fragments within the same Activity. Multiple UI components can observe the same ViewModel instance and receive updates when the underlying data changes. This eliminates the need for complex data passing mechanisms between components.
  5. Testing: ViewModel can be easily unit-tested since it contains the business logic separate from the UI components. You can write tests to verify the correctness of data transformations, data loading, and other operations performed by the ViewModel.

To use the ViewModel, you need to follow these steps:

  1. Create a subclass of the ViewModel class and define your data-related operations within it.
  2. Associate the ViewModel with the lifecycle of your UI component by obtaining an instance of it using the ViewModelProvider or by viewModels() Kotlin property delegate.
  3. Observe the data exposed by the ViewModel in your UI components using LiveData or Kotlin Flow, allowing the UI to update automatically when the data changes.

ViewModelProvider:

The ViewModelProvider is a utility class in Android's ViewModel library that facilitates the creation and retrieval of ViewModel instances associated with a particular lifecycle owner, such as an Activity or Fragment. It is responsible for managing the lifecycle of ViewModel instances and providing the correct instance based on the lifecycle state.

The ViewModelProvider class offers several methods to obtain ViewModel instances, depending on the use case:

  1. ViewModelProvider(activity: tActivity): This constructor is used to create a ViewModelProvider associated with a Activity. The ViewModelProvider will retain the ViewModel instances across configuration changes in the activity.
  2. ViewModelProvider(fragment: Fragment): This constructor is used to create a ViewModelProvider associated with a Fragment. The ViewModelProvider will retain the ViewModel instances across configuration changes in the fragment.
  3. ViewModelProvider(Activity: Activity, factory: ViewModelProvider.Factory): This constructor allows you to provide a custom ViewModelProvider.Factory implementation to instantiate the ViewModel instances. This is useful when you need to pass constructor arguments to the ViewModel.
  4. ViewModelProvider(fragment: Fragment, factory: ViewModelProvider.Factory): This constructor allows you to provide a custom ViewModelProvider.Factory implementation to instantiate the ViewModel instances for a specific Fragment.

Once you have obtained an instance of the ViewModelProvider, you can use its get() method to retrieve the ViewModel associated with a given class:

val viewModel = ViewModelProvider(this).get(MyViewModel::class.java)         

In the above example, this refers to the lifecycle owner (such as an Activity or Fragment) that will be used as the key to associate the ViewModel with. The get() method retrieves the ViewModel instance of the specified class. If a ViewModel instance already exists for the given lifecycle owner, it will be returned; otherwise, a new ViewModel instance will be created.

Under the hood, the ViewModelProvider uses a ViewModelStore to store and manage the ViewModel instances.


Example that demonstrates how to use the ViewModelProvider with a custom factory to create a ViewModel instance:

  1. First, define your ViewModel class. For example, let's say you have a TaskViewModel:

class TaskViewModel(private val taskRepository: TaskRepository) : ViewModel() {
? ? // ViewModel logic and data manipulation methods go here
}        


2.Create a factory class that implements the ViewModelProvider.Factory interface. This factory will be responsible for creating instances of your ViewModel. For example:

class TaskViewModelFactory(private val taskRepository: TaskRepository) : ViewModelProvider.Factory {
? ? override fun <T : ViewModel?> create(modelClass: Class<T>): T {
? ? ? ? if (modelClass.isAssignableFrom(TaskViewModel::class.java)) {
? ? ? ? ? ? return TaskViewModel(taskRepository) as T
? ? ? ? }
? ? ? ? throw IllegalArgumentException("Unknown ViewModel class")
? ? }
}        

In the above example, the factory receives an instance of the TaskRepository, which is a dependency required by the TaskViewModel.


3.In your Activity class, create an instance of the TaskViewModelFactory and use it to create the ViewModel instance:

class MainActivity : AppCompatActivity() {
? ? private lateinit var taskViewModel: TaskViewModel

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

? ? ? ? val taskRepository = TaskRepository() // Create an instance of the TaskRepository

? ? ? ? val viewModelFactory = TaskViewModelFactory(taskRepository) // Create the ViewModel factory

? ? ? ? taskViewModel = ViewModelProvider(this, viewModelFactory).get(TaskViewModel::class.java)
? ? ? ? // Retrieve the ViewModel instance using the factory and ViewModelProvider

? ? ? ? // Now you can use the taskViewModel to interact with your ViewModel logic
? ? }
}        

In the above code, we first create an instance of the TaskRepository. Then we create an instance of the TaskViewModelFactory, passing the taskRepository as a constructor parameter. Finally, we use the ViewModelProvider to retrieve the TaskViewModel instance by passing the Activity and the factory.

By following this approach, the ViewModelProvider will handle the creation and retention of the ViewModel instance, ensuring that the same instance is retained across configuration changes. The ViewModelFactory is responsible for providing the required dependencies to the ViewModel during its creation.

Make sure to replace the TaskRepository, TaskViewModel, and MainActivity with your own classes and adjust the code according to your application's requirements.

This example demonstrates how to use the ViewModelProvider with a custom factory, which allows you to inject dependencies into your ViewModel. It provides a more flexible way to create ViewModels with complex dependencies while still benefiting from the lifecycle-aware features provided by the ViewModel architecture.

Viewmodelstore :

In Android, the ViewModelStore is a class that is responsible for storing and managing ViewModel instances. It is typically used in conjunction with the ViewModelProvider to retain ViewModel instances across configuration changes, such as screen rotations.

The ViewModelStore is associated with a specific lifecycle owner, such as an Activity or Fragment, and it ensures that the ViewModel instances are scoped to that lifecycle owner. It allows the ViewModel instances to survive configuration changes and provides a way to retrieve the same ViewModel instance when the lifecycle owner is recreated.

When you create a ViewModel instance using the ViewModelProvider, the ViewModelStore is used to store and manage that instance. The ViewModelStore keeps track of the ViewModel instances and associates them with a unique key, typically based on the lifecycle owner. This allows the ViewModelProvider to retrieve the correct ViewModel instance when requested, based on the provided key.

The ViewModelStore is internally managed by the lifecycle owner, and it gets cleared and recreated as necessary during the lifecycle of the owner. For example, when an Activity is destroyed and recreated due to a configuration change, the ViewModelStore is cleared and then populated with the ViewModel instances again, ensuring that the same ViewModel instances are available to the recreated Activity.

The ViewModelStore is an important component in the ViewModel architecture pattern because it enables the separation of concerns between the UI components (Activity or Fragment) and the ViewModel instances. It allows the ViewModel instances to hold and manage the data required by the UI components, while the UI components can focus on handling the user interface logic.

By using the ViewModelStore and ViewModelProvider together, you can ensure that the ViewModel instances are retained and associated with the appropriate lifecycle owner, making it easier to manage the state of your app's UI across configuration changes.


LiveData:

LiveData is a data holder class provided by the Android Architecture Components. It is designed to hold and emit data in a lifecycle-aware manner. LiveData is observable, which means it can be observed by other components, such as activities or fragments, to react to changes in the data.

Here are the key characteristics of LiveData:

  1. Lifecycle awareness: LiveData is aware of the lifecycle state of the components that observe it. It only sends updates to active and started components, ensuring that they receive the latest data when they are in a valid state to handle it. This helps prevent memory leaks and crashes due to stale data.
  2. Automatic updates: LiveData provides automatic updates to the observers whenever the data it holds changes. This eliminates the need for manual updates and reduces boilerplate code.
  3. Data persistence: LiveData retains the latest emitted value when the configuration changes, such as screen rotation. This ensures that the observers receive the most recent data without the need for explicit handling.

MutableLiveData is a subclass of LiveData that allows you to modify the data it holds. It provides methods to update the value, such as setValue() and postValue(). MutableLiveData is commonly used when the data needs to be modified by the observed component, such as an activity or fragment.

Here's an example that demonstrates the usage of LiveData and MutableLiveData:

class MyViewModel : ViewModel() {
? ? private val _name = MutableLiveData<String>() // MutableLiveData to hold a string value
? ? val name: LiveData<String> = _name // Expose the LiveData as a read-only property

? ? fun updateName(newName: String) {
? ? ? ? _name.value = newName // Update the value of the MutableLiveData
? ? }
}        

In the above example, we define a ViewModel called MyViewModel. It has a private MutableLiveData property called _name that holds a string value. We expose the _name property as a read-only LiveData using the name property. The updateName() function allows modifying the value of _name by calling setValue().

In an activity or fragment, you can observe the LiveData using the observe() function:

class MainActivity : AppCompatActivity() {
? ? private lateinit var viewModel: MyViewModel


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


? ? ? ? viewModel = ViewModelProvider(this).get(MyViewModel::class.java)


? ? ? ? viewModel.name.observe(this, Observer { newName ->
? ? ? ? ? ? // React to changes in the name LiveData
? ? ? ? ? ? textView.text = newName
? ? ? ? })


? ? ? ? button.setOnClickListener {
? ? ? ? ? ? viewModel.updateName("John Doe")
? ? ? ? }
? ? }
}        

In the above code, we observe the name LiveData in the onCreate() method of the activity. Whenever the value of name changes, the observer's onChanged() callback will be called with the new value. In this case, we update a TextView with the new name. When the button is clicked, the updateName() function is called, which updates the value of the LiveData and triggers the observer callback.

What is Observer in the above code ?

Observer refers to an interface provided by the Android Architecture Components. It is used to observe changes in a LiveData object.

In the above code snippet, viewModel.name is an instance of LiveData, which represents a data stream that can be observed for changes. The observe() function is used to register an observer on the LiveData object. It takes two parameters: the lifecycle owner (in this case, this refers to the current activity or fragment) and an instance of the Observer interface.

The Observer interface has a single method called onChanged(), which is invoked whenever the observed LiveData emits a new value. Inside the onChanged() method, you can define the logic to react to the changes in the LiveData.

In the given example, when the name LiveData emits a new value, the onChanged() method is triggered with the new value (newName). The code inside the onChanged() block updates the textView with the new name value.

By using the Observer pattern and LiveData, you can establish a reactive relationship between the data (LiveData) and the UI components (such as TextView), ensuring that the UI always reflects the latest state of the data.


LiveData and MutableLiveData are powerful tools for implementing reactive data flows in Android applications. They provide a simple and efficient way to handle data changes and ensure that components are always observing the latest data while being aware of the lifecycle state.

Internal design of LiveData:

LiveData is designed to be a lifecycle-aware data holder that follows the observer pattern. It is built upon the concepts of observers, publishers, and lifecycle owners. Internally, LiveData utilizes several key components and mechanisms to provide its functionality.

  1. Observer pattern: At its core, LiveData implements the observer pattern. It maintains a list of registered observers that are notified when the data held by LiveData changes. Observers can be added or removed dynamically based on the lifecycle state of the owner.
  2. Lifecycle awareness: LiveData is lifecycle-aware, meaning it is designed to be aware of the lifecycle state of the components that observe it. It uses the lifecycle library to observe the lifecycle state of the lifecycle owner (e.g., Activity or Fragment). This allows LiveData to automatically start or stop sending updates to the observers based on the current lifecycle state.
  3. LiveData value: LiveData holds its value internally using a private field. The value can be of any type, such as a primitive type or a custom object. The value is stored as an object reference, which allows LiveData to track changes to the value.
  4. Publishing updates: When the value of LiveData changes, it notifies all registered observers by calling their onChanged() method. This is typically done through the use of a notifyObservers() mechanism, similar to the traditional observer pattern.
  5. Threading: LiveData is designed to be used on the main thread by default. It ensures that observer callbacks are dispatched on the main thread to allow for easy UI updates. LiveData also provides mechanisms for handling background threads, such as postValue() which allows updating the value from a background thread.
  6. Lifecycle owner integration: LiveData integrates with the lifecycle library, specifically the LifecycleOwner interface. This integration allows LiveData to automatically manage the observer registration and unregistration based on the lifecycle state of the owner. When a lifecycle owner is destroyed, LiveData automatically removes any registered observers to prevent memory leaks.


Internal implementation of LiveData is quite complex and involves various components and mechanisms. However, I below is simplified example to demonstrate how LiveData can be implemented at a high level. Please note that this is not the exact implementation used in the Android framework, but rather a simplified version for illustrative purposes. The one in Android framework is more sophisticated. It includes additional features like lifecycle awareness, background thread handling, and more.

Here's a basic example of how LiveData can be implemented:

class LiveData<T> {
? ? private val observers = mutableListOf<Observer<T>>()
? ? private var value: T? = null
? ??
? ? fun addObserver(observer: Observer<T>) {
? ? ? ? observers.add(observer)
? ? }
? ??
? ? fun removeObserver(observer: Observer<T>) {
? ? ? ? observers.remove(observer)
? ? }
? ??
? ? fun setValue(value: T) {
? ? ? ? this.value = value
? ? ? ? notifyObservers()
? ? }
? ??
? ? private fun notifyObservers() {
? ? ? ? for (observer in observers) {
? ? ? ? ? ? observer.onChanged(value)
? ? ? ? }
? ? }
}

interface Observer<T> {
? ? fun onChanged(value: T?)
}        

In this simplified implementation, LiveData is a generic class that holds a value of type T. It also maintains a list of registered observers of type Observer<T>. The addObserver() and removeObserver() methods allow adding and removing observers dynamically.

The setValue() method is responsible for updating the value of LiveData. Whenever the value changes, the notifyObservers() method is called to iterate through all the registered observers and notify them by invoking their onChanged() method.

The Observer interface defines the onChanged() method, which is called when the value of LiveData changes. Observers can implement this method to perform any necessary actions based on the new value.


Thanks for reading till end . Please comment if you have any.

Steven Bradley

Alternant Développeur éditique chez Sopra Banking Software

6 个月

Hi, I was looking for a way to refresh my memory on MVVM architecture and your article appeared on my search. I'm glad I found it since it's a condensed nest of key information, so I wanted to thoroughly thank you for writing it. Can I ask though, you mentioned a potential overuse of observable, I wasn't aware of the fact that it could drastically reduce performances. Do you have an idea around how many observables it takes for that to happen ? Or in general, in what condition is usage of observables considered an overuse ?

要查看或添加评论,请登录

Amit Nadiger的更多文章

  • Atomics in Rust

    Atomics in Rust

    Atomics in Rust are fundamental building blocks for achieving safe concurrent programming. They enable multiple threads…

  • Frequently used Thread API - Random notes

    Frequently used Thread API - Random notes

    Thread Creation and Management: thread::spawn: Creates a new thread and executes a closure within it. It returns a…

  • Difference b/w Cell and RefCell

    Difference b/w Cell and RefCell

    Both Cell and RefCell are used in Rust to introduce interior mutability within immutable data structures, which means…

  • Tokio::spawn() in depth

    Tokio::spawn() in depth

    Tokio::spawn() is a function provided by the Tokio runtime that allows you to create a new concurrent task. Unlike…

  • tokio::spawn() Vs Async block Vs Async func

    tokio::spawn() Vs Async block Vs Async func

    Asynchronous programming is a powerful paradigm for handling I/O-bound operations efficiently. Rust provides several…

  • Tokio Async APIS - Random notes

    Tokio Async APIS - Random notes

    In this article, we will explore how to effectively use Tokio and the Futures crate for asynchronous programming in…

  • Reactor and Executors in Async programming

    Reactor and Executors in Async programming

    In asynchronous (async) programming, Reactor and Executor are two crucial components responsible for managing the…

  • Safe Integer Arithmetic in Rust

    Safe Integer Arithmetic in Rust

    Rust, as a systems programming language, emphasizes safety and performance. One critical aspect of system programming…

  • iter() vs into_iter()

    iter() vs into_iter()

    In Rust, iter() and into_iter() are methods used to create iterators over collections, but they have distinct…

  • Zero-cost abstraction in Rust

    Zero-cost abstraction in Rust

    Rust supports zero-cost abstractions by ensuring that high-level abstractions provided by the language and standard…

社区洞察

其他会员也浏览了