The Foundations of MVVM, Inheritance, and Interfaces – Day 8 Android 14 Masterclass
Welcome to Day 8 of the Android 14 Masterclass, where we delve deep into the intricacies of MVVM (Model-View-ViewModel) architecture, inheritance, interfaces, and the role of repositories and APIs in Android development. Today, we focus on the foundational elements that make MVVM a robust and efficient pattern for organizing code. We will also dissect the concepts of inheritance and interfaces, which are pillars of object-oriented programming and instrumental in creating a scalable, modular application.
1. Understanding MVVM
MVVM stands for Model-View-ViewModel. It is an architectural pattern used in software development, which helps in organizing your code in a way that is easier to understand, test, and maintain.
Let’s break down what each component in MVVM does:
1. Model
- What it is: The Model represents the data and business logic of the application. It is responsible for retrieving and storing data, as well as performing any necessary data processing.
- Example: If you have an app that displays a list of books, the Model would be responsible for fetching the book data from a database or an online source.
2. View
- What it is: The View is the user interface (UI) of the application. It displays the data to the user and interacts with the user.
- Example: In the same book app, the View would be the actual screen that displays the list of books to the user.
3. ViewModel
- What it is: The ViewModel acts as a bridge between the Model and the View. It takes data from the Model, applies UI logic, and then formats it for display in the View.
- Example: For the book app, the ViewModel would take the raw book data, and format it nicely for display in the View.
Syntax and Code Examples: MVVM
Here’s a simplified example in Kotlin to give you a basic idea of how MVVM works:
- Model
data class Book(val title: String, val author: String)
- ViewModel
class BookViewModel { fun getBooks(): List<Book> { //Here you would normally fetch data from a database or online source return listOf(Book("Title1", "Author1"), Book("Title2", "Author2")) } }
- View
- Your View would be an XML layout file where you design your UI.
- You would also have a Kotlin file where you would interact with the ViewModel to get the data and display it.
class BookActivity : AppCompatActivity() { private lateinit var viewModel: BookViewModel override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_book) viewModel = BookViewModel() val books = viewModel.getBooks() // Now you can display the books in your UI } }
In this example, the
Book
class is the Model,BookViewModel
is the ViewModel, andBookActivity
is the View. The ViewModel fetches the book data, and the View displays it.Remember, this is a simplified example. In a real-world application, you would have more complex interactions, and you might fetch data from a database or an online API. You would also implement more advanced features like data binding and LiveData to make the app more responsive and user-friendly.
2. MVVM Architecture: What is a ViewModel Class?
A ViewModel Class in Android is a part of the MVVM architecture, as previously mentioned. It acts as a manager that handles the communication between the app’s data and the UI.
The ViewModel is responsible for holding and processing all the data needed for the UI while respecting the lifecycle of the app’s activities or fragments.
Why is ViewModel Important?
- Lifecycle Awareness: ViewModel is designed to store and manage UI-related data in a lifecycle-conscious way. It allows data to survive configuration changes such as screen rotations.
- Separation of Concerns: ViewModel helps to keep the UI code simple and focused on presenting data, as it takes care of the data handling.
How Does ViewModel Work?
- Initialization: A ViewModel is usually initialized in an activity or fragment. It survives configuration changes and is destroyed when the activity or fragment is permanently removed.
- Data Handling: ViewModel retrieves data from the Model, holds it, and the View observes this data to update the UI accordingly.
Basic Syntax and Code Example
Let’s look at a simple example to understand the ViewModel Class better:
- Creating a ViewModel Class
import androidx.lifecycle.ViewModel
class MyViewModel : ViewModel() {
var number: Int = 0
fun incrementNumber() {
number++
}
}
In this example, MyViewModel
is a class that extends ViewModel()
. It has a variable number
and a function incrementNumber()
to increase the number by one.
- Using ViewModel in an Activity
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
class MainActivity : AppCompatActivity() {
private val myViewModel: MyViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Use the ViewModel
myViewModel.incrementNumber()
}
}
In the MainActivity
, the ViewModel is initialized and used. The by viewModels()
delegate is used to associate the ViewModel with the activity.
Key Points to Remember
- ViewModel is not a replacement for onSaveInstanceState; it doesn’t handle all types of configuration changes.
- ViewModel should never contain references to Views, Activities, Fragments, or any Context, as this can cause memory leaks.
3. Introduction to Inheritance
Inheritance is a fundamental concept in object-oriented programming (OOP), and it is widely used in Android development.
Inheritance allows a class to use methods and fields of another class, promoting reusability and a hierarchical organization of code.
Understanding Basic Concepts
- Base Class (Parent Class): The class whose properties and methods are inherited by another class. It is also known as the superclass or parent class.
- Derived Class (Child Class): The class that inherits the properties and methods from another class. It is also known as the subclass or child class.
- Open Keyword: In Kotlin, classes are final by default, which means they can’t be inherited. To allow a class to be inherited, it must be declared with the
open
keyword.
Explaining Inheritance with an Example
Imagine you are creating an application that has several types of vehicles, like cars and bicycles. You can create a general class named Vehicle
and then create more specific classes like Car
and Bicycle
that inherit from Vehicle
.
- Base Class (Parent Class)
open class Vehicle {
fun start() {
println("The vehicle is starting.")
}
}
Here, Vehicle
is the base class. It has a method start()
that prints a message. The open
keyword allows this class to be inherited.
- Derived Class (Child Class)
class Car : Vehicle() {
fun drive() {
println("The car is driving.")
}
}
Car
is a derived class that inherits from Vehicle
. It has an additional method drive()
.
- Using the Classes
val myCar = Car()
myCar.start() // Output: The vehicle is starting.
myCar.drive() // Output: The car is driving.
When you create an object of the Car
class, you can access both the methods from the Car
class and the inherited methods from the Vehicle
class.
Key Points to Remember
- Inheritance helps in reusing and organizing code efficiently.
- The
open
keyword is essential to allow inheritance in Kotlin. - The derived class has access to the public and protected members (methods and fields) of the base class.
Inheritance in Android development, and programming in general, allows for a structured and reusable way of writing code. It enables derived classes to inherit features from base classes, making the code more modular and easier to manage and understand, especially as applications become more complex.
4. Open Function in Kotlin
In Kotlin, the open
keyword is not limited to classes; it is also used with functions. By default, functions in Kotlin are final
, meaning they can’t be overridden in a derived class.
When a function is declared with the open
keyword, it allows that function to be overridden by derived classes, enabling them to provide a specific implementation.
Why Use Open Functions?
Using open
functions is essential when you want to provide a default behavior that can be customized by derived classes. It promotes flexibility and allows derived classes to modify or extend the functionality of a function from the base class.
Explaining Open Function with an Example
Let’s continue with the vehicle example to understand open
functions better.
- Base Class with Open Function
open class Vehicle {
open fun start() {
println("The vehicle is starting.")
}
}
Here, the start()
function is marked as open
, allowing it to be overridden by derived classes.
- Derived Class Overriding the Open Function
class Car : Vehicle() {
override fun start() {
println("The car is starting with a roar!")
}
}
In the Car
class, the start()
function is overridden, providing a new implementation that is specific to the Car
class.
- Using the Classes
val myVehicle = Vehicle()
myVehicle.start() // Output: The vehicle is starting.
val myCar = Car()
myCar.start() // Output: The car is starting with a roar!
When you call the start()
function on objects of the Vehicle
and Car
classes, the output will be based on their respective implementations of the function.
Key Points to Remember
- An
open
function allows derived classes to provide a specific implementation by overriding it. - Overriding is declaring a function in the derived class with the same name and parameters as a function in the base class.
Open
functions in Kotlin are a powerful feature that allows for more flexible and reusable code. They enable derived classes to customize or extend the functionality of functions declared in the base class, enhancing the adaptability of your code in various scenarios and use cases.
5. Override Function in Kotlin
n Kotlin, when a function in a derived class has the same name as a function in its base class, and the function in the base class is marked as open
, the derived class has the option to provide a new implementation for this function. This is known as overriding the function.
Using the override
Keyword
The override
keyword is used in the derived class to modify the function and provide a new implementation.
Understanding the super
Keyword
The super
keyword is used inside the overridden function to call the function of the base class. It allows the derived class to use the original implementation of the function.
Explaining with Examples
- Override Function Without Using
super
open class Vehicle {
open fun start() {
println("The vehicle is starting.")
}
}
class Car : Vehicle() {
override fun start() {
println("The car is starting with a roar!")
}
}
Here, the Car
class overrides the start()
function without calling the base class function, providing a completely new implementation.
- Override Function Using
super
class SportCar : Vehicle() {
override fun start() {
super.start()
println("The sports car is ready to zoom!")
}
}
In this example, the SportCar
class overrides the start()
function and uses the super
keyword to call the base class function, adding additional behavior.
- Using the Classes
val car = Car()
car.start() // Output: The car is starting with a roar!
val sportCar = SportCar()
sportCar.start()
// Output: The vehicle is starting.
// The sports car is ready to zoom!
Different outputs are displayed based on whether the super
keyword is used in the overridden function.
Key Points to Remember
- The
override
keyword is essential for providing a new implementation to an open function in the base class. - The
super
keyword allows accessing functions of the base class, enabling the reuse of code.
Overriding functions in Kotlin allows derived classes to provide specific implementations of functions from the base class, enabling customization and extended functionality.
The use of the super
keyword further enhances this by allowing derived classes to build upon the existing implementations in the base class, promoting code reusability and a hierarchical structure in the codebase.
6. Understanding Interfaces
Interfaces in Kotlin are a way to define a contract for classes without implementing any behavior.
Interfaces can contain abstract methods (methods without a body) and methods with a default implementation. Classes that implement an interface must provide implementations for all of its abstract methods.
Why Use Interfaces?
- Multiple Inheritance: Interfaces allow a class to inherit functionalities from multiple sources, as a class can implement multiple interfaces.
- Flexibility: Interfaces provide a way to define methods that must be implemented by a class, ensuring that certain functionalities are present.
Explaining Interfaces with an Example
- Defining an Interface
interface Drivable {
fun drive()
}
Here, an interface Drivable
is defined with an abstract method drive()
.
- Implementing an Interface
class Car : Drivable {
override fun drive() {
println("The car is driving.")
}
}
The Car
class implements the Drivable
interface and provides an implementation for the drive()
method.
- Using the Interface
val myCar = Car()
myCar.drive() // Output: The car is driving.
An object of the Car
class is created, and the drive()
method is called.
- Interface with Default Implementation
interface Drivable {
fun drive() {
println("Driving the vehicle.")
}
}
class Bicycle : Drivable
val myBicycle = Bicycle()
myBicycle.drive() // Output: Driving the vehicle.
The interface has a default implementation of the drive()
method, so the Bicycle
class doesn’t need to provide an implementation.
Key Points to Remember
- Interfaces can contain abstract methods and methods with default implementations.
- A class can implement multiple interfaces, providing a form of multiple inheritance.
- When a class implements an interface, it needs to provide implementations for all its abstract methods.
Interfaces in Kotlin offer a powerful way to define contracts for classes, ensuring that they implement specific functionalities.
They provide flexibility and allow for a form of multiple inheritance, enabling classes to inherit functionalities from multiple sources, leading to a more organized and reusable code structure.
7. What are Repositories in Android Development?
In the context of Android development, particularly when following the MVVM (Model-View-ViewModel) architecture, a Repository is a class that acts as a clean API for data access to the rest of the application.
It abstracts the origin of the data, which can come from a network source, caching, or a local database.
MVVM Architecture: Why Use Repositories?
- Decoupling: Repositories allow for decoupling of the data sources from the rest of the application. The ViewModel interacts with a Repository, and it doesn’t need to know where the data comes from.
- Data Aggregation: A Repository can manage and coordinate data from multiple sources, providing a unified API.
- Offline Capability: Repositories can cache network data, allowing apps to work offline and providing a better user experience.
Explaining Repositories with an Example
Imagine you are building an app that displays user profiles. The data can come from a local database or a network source.
- Defining a Repository
class UserRepository(private val userDao: UserDao,
private val userService: UserService) {
fun getUser(userId: String): LiveData<User> {
// Logic to fetch data from network or local database
}
}
UserRepository
is a class that takes a UserDao
(local database) and a UserService
(network service) as parameters.
- Using the Repository in a ViewModel
class UserViewModel(private val repository: UserRepository) : ViewModel() {
fun getUser(userId: String): LiveData<User> {
return repository.getUser(userId)
}
}
UserViewModel
interacts with UserRepository
to get the user data. It doesn’t know whether the data comes from the network or a local database.
- Accessing Data in the View
The View observes the data provided by the ViewModel and updates the UI accordingly.
Key Points to Remember
- Repositories abstract the origin of the data, providing a clean API to the rest of the application.
- Repositories can manage data from multiple sources, such as network sources and local databases.
- Using Repositories makes the code more modular, maintainable, and testable.
Repositories play a crucial role in Android application architecture by acting as a clean API for data access, managing data sources, and ensuring that the data flows seamlessly through the different layers of the application.
Understanding and implementing Repositories will help in creating robust, maintainable, and user-friendly Android applications.
8. MVVM Architecture: APIs Briefly
APIs, or Application Programming Interfaces, are sets of rules and protocols that allow one software application to interact with another. They define the methods and data formats that applications can use to communicate with each other.
APIs are used to integrate different software systems, enabling them to work together, share data, and enhance functionality.
In simpler terms, think of an API as a menu in a restaurant. The menu offers a list of dishes you can order, along with a description of each dish. When you specify which dish you want, the kitchen (i.e., the system) prepares the dish and serves it.
In this analogy, the menu is the API, the order is the request, and the dish that is served to you is the response.
Conclusion: The Foundations of MVVM, Inheritance, and Interfaces – Day 8 Android 14 Masterclass
After exploring the foundations and advanced concepts of MVVM, inheritance, and interfaces, it’s clear that these constructs are more than just theoretical concepts; they are practical tools that enable the creation of robust and maintainable Android applications. We’ve seen how a well-structured MVVM pattern enhances testability and maintainability, while inheritance and interfaces contribute to a clean and efficient codebase. As we wrap up Day 8 of the Android 14 Masterclass, remember that the power of these principles lies in their proper implementation. Use them wisely to build apps that not only function seamlessly across different Android versions and devices but also provide a seamless and enjoyable experience for the end-users.
If you want to skyrocket your Android career, check out our The Complete Android 14 & Kotlin Development Masterclass. Learn Android 14 App Development From Beginner to Advanced Developer.
Master Jetpack Compose to harness the cutting-edge of Android development, gain proficiency in XML — an essential skill for numerous development roles, and acquire practical expertise by constructing real-world applications.
Check out Day 6 of this course here.
Check out Day 7 of this course here.
Check out Day 9 of this course here.