Share
Explore

Lab Workbook: Data Persistence with Firebase Lab Learning Workbook for Android with Kotlin Development

Introduction: (this lab has some duplication to clean up)
In this lab learning workbook, we'll explore data persistence using Firebase in Android with Kotlin development.
We'll cover Android Jetpack Architecture Components, understanding the Firebase database structure, creating documents & collections with Kotlin code, and CRUD operations using Kotlin.

Section 1: Android Jetpack Architecture Components

Introduction to Android Jetpack Architecture Components
LiveData, ViewModel, and Room
Implementing Architecture Components in an Android project using Kotlin

———————

1.1 Introduction to Android Jetpack Architecture Components

Android Jetpack is a suite of libraries, tools, and guidance for developing robust Android applications[2]. It includes Foundation Components, Architecture Components, Behavior Components, and UI Components. In this section, we'll focus on Architecture Components, which help developers avoid lifecycle-related issues and maintain applications more efficiently[2].

1.2 LiveData, ViewModel, and Room

Three key Architecture Components are LiveData, ViewModel, and Room[1]. LiveData is a lifecycle-aware, observable data holder class that keeps data up-to-date and prevents memory leaks[3]. ViewModel is responsible for managing UI-related data, allowing data to survive configuration changes[1]. Room is an SQLite object-mapping library that overcomes SQLite's drawbacks, such as compile-time query checking and the inability to save POJOs[2].

1.3 Implementing Architecture Components in an Android project using Kotlin

To implement Architecture Components in your Android project using Kotlin, follow these steps:
Add the necessary dependencies to your app's build.gradle file[1].
Create a data class to represent your app's data model and add annotations for Room entities[1].
Define a Data Access Object (DAO) interface to interact with the database[1].
Create a Room database class that extends RoomDatabase[1].
Implement a ViewModel to interact with your data model and expose LiveData objects for the UI components[1].
Update your Activity or Fragment to observe LiveData objects and display the data[3].

References:

———————-


Introduction to Android Jetpack Architecture Components:

Android Jetpack Architecture Components is a set of libraries, tools, and guidance that help developers create robust, testable, and maintainable Android apps. It includes a collection of libraries that are built on top of the Android framework and provides a set of best practices, patterns, and guidelines for Android app development. The Architecture Components offer a way to design and develop Android apps that are modular, scalable, and easy to maintain.
The four main components of the Android Jetpack Architecture Components are:
Foundation Components - These components provide a base layer for Android app development, including Kotlin extensions, app compat, and testing.
Architecture Components - These components provide a set of libraries that help developers to build a robust and scalable architecture for their Android apps. Some of the key components of Architecture Components are LiveData, ViewModel, Room, and Navigation.
Behavior Components - These components help developers to add common Android app features such as notifications, permissions, and sharing to their apps.
UI Components - These components help developers to build user interfaces for their Android apps using pre-built components such as RecyclerView, CardView, and ConstraintLayout.

LiveData, ViewModel, and Room:

LiveData, ViewModel, and Room are some of the key components of the Architecture Components that help developers to build a robust and scalable architecture for their Android apps.
LiveData: LiveData is an observable data holder class that is part of the Architecture Components. It allows you to observe changes to data in real-time and update the UI accordingly. LiveData is lifecycle-aware, which means it automatically manages the lifecycle of the data it holds and ensures that the UI is always up-to-date. LiveData is also thread-safe, which means it can be safely accessed from multiple threads.
ViewModel: ViewModel is a class that is part of the Architecture Components. It is designed to store and manage UI-related data in a lifecycle-aware way. ViewModel is responsible for preparing and managing data for the UI. It survives configuration changes, such as screen rotations, so the data is not lost. ViewModel can also communicate with other components, such as LiveData, to provide data to the UI.
Room: Room is a persistence library that is part of the Architecture Components. It provides an abstraction layer over SQLite to allow you to easily store and access data in your Android app. Room uses annotations to generate SQL code for you, which reduces the amount of boilerplate code you need to write. Room also provides compile-time verification of SQL queries, which can help you catch errors early.

Implementing Architecture Components in an Android project using Kotlin:

To implement the Architecture Components in an Android project using Kotlin, follow these steps:
Add the Architecture Components to your project: To use the Architecture Components in your project, you need to add the following dependencies to your app's build.gradle file:

dependencies {
def lifecycle_version = "2.4.0"
def room_version = "2.4.0"

implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
implementation "androidx.room:room-ktx:$room_version"
}

Create a ViewModel: To create a ViewModel, you need to create a new class that extends the ViewModel class.
In the ViewModel class, you can define properties to hold your data and methods to manipulate that data. Here is an example of a ViewModel class that holds a list of users:
class UserViewModel : ViewModel() {

private val userRepository = UserRepository()

fun getUsers(): LiveData<List<User>> {
return userRepository.getUsers()
}

fun addUser(user: User) {
userRepository.addUser(user)
}

Section 2: Understanding Firebase Database Structure

=====================

Introduction to Firebase Realtime Database and Firestore:

Firebase is a backend-as-a-service (BaaS) platform that provides a range of services for mobile and web application development.
Firebase Realtime Database and Firestore are two popular database options provided by Firebase.
Firebase Realtime Database is a cloud-hosted NoSQL database that stores data in JSON format.
It allows developers to store and sync data in real-time between clients, making it ideal for applications that require real-time updates such as chat apps or live streaming.
Firestore, on the other hand, is a cloud-hosted NoSQL document database that stores data in collections and documents.
Firestore is designed to scale horizontally and can handle large datasets with complex relationships between data.

Firebase Database Structure: JSON tree (Realtime Database) vs.

Collections and Documents (Firestore)

Firebase Realtime Database uses a JSON tree structure to store and retrieve data.
The data is organized as a hierarchy of nodes, with each node represented as a key-value pair. The nodes can have child nodes, and the data is stored in a nested fashion.
For example, consider a simple Firebase Realtime Database structure:

{
"users": {
"user1": {
"name": "John",
"age": 25
},
"user2": {
"name": "Mary",
"age": 30
}
}
}

In this example, the "users" node has two child nodes, "user1" and "user2", each containing data for a user.
Firestore, on the other hand, uses a collections and documents structure to store data.
A collection is a group of documents, and each document is a set of key-value pairs.
The documents in a collection do not have to have the same structure, allowing for more flexibility in data modeling. For example, consider a simple Firestore structure:

users (collection)
user1 (document)
name: "John"
age: 25
user2 (document)
name: "Mary"
age: 30

In this example, "users" is a collection, and "user1" and "user2" are documents in that collection.

Choosing the right database for your project

The choice between Firebase Realtime Database and
Firestore
depends on the specific requirements of your project. If your application requires real-time updates and a simple data structure, Firebase Realtime Database is a good choice.
On the other hand, if you need more complex queries and a flexible data model, Firestore is a better option.
In addition, Firebase Realtime Database has better support for offline data,
while Firestore offers more advanced security features such as role-based access control.
To use either Firebase Realtime Database or Firestore in your application, you need to integrate the Firebase SDK into your project and initialize the database.
You can then use the Firebase APIs to read and write data to the database.
The Firebase SDK provides a set of libraries for various platforms, including Android, iOS, and web.
========================
Introduction to Firebase Realtime Database and Firestore
Firebase Database Structure: JSON tree (Realtime Database) vs. Collections and Documents (Firestore)
Choosing the right database for your project

2.1 Introduction to Firebase Realtime Database and Firestore

Firebase provides two cloud-hosted databases for your mobile and web applications:
Realtime Database and
Cloud Firestore.
Both databases enable real-time data synchronization, offline support, and scalability.
However, they have different data structures, querying capabilities, and pricing models.

2.2 Firebase Database Structure: JSON tree (Realtime Database) vs. Collections and Documents (Firestore)
Realtime Database stores data as a large JSON tree. Each node in the tree is accessible via a unique URL, and you can store simple data types like strings, numbers, booleans, and objects. There's no support for complex queries in Realtime Database
Firestore, on the other hand, uses a document-oriented model where data is stored in documents organized into collections. Each document is a set of key-value pairs, and you can have subcollections within documents. Firestore supports complex queries and provides features like atomic transactions and offline support

2.3 Choosing the right database for your project

To choose the right database for your project, consider the following factors:
Data structure: If your data is hierarchical or has simple relationships, Realtime Database might be a better fit. If your data requires complex queries or has more intricate relationships, Firestore is the better choice
Query capabilities: Realtime Database supports basic queries, while Firestore supports more advanced querying, like compound and composite indexing.
Pricing: Realtime Database charges based on the amount of data stored, data transferred, and simultaneous connections. Firestore charges based on the number of reads, writes, and deletes, as well as data storage and network usage.
Scalability: Firestore offers better automatic scaling, while Realtime Database requires manual scaling by sharding data across multiple databases
Consider these factors when choosing between Firebase Realtime Database and Firestore for your Android application with Kotlin development.

Section 3: Creating Documents and Collections with Android Kotlin Code

3.1 Setting up Firebase in your Android project

To set up Firebase in your Android project, follow these steps:
Go to the Firebase console (https://console.firebase.google.com/) and create a new project.
Click on "Add Firebase to your Android app" and provide your package name.
Download the google-services.json file and place it in your Android project's app folder.
Add the required dependencies to your project-level and app-level build.gradle files.
// Project-level build.gradle
buildscript {
dependencies {
classpath 'com.google.gms:google-services:4.3.10'
}
}


// App-level build.gradle
dependencies {
implementation 'com.google.firebase:firebase-firestore-ktx:23.0.3'
}

apply plugin: 'com.google.gms.google-services'


3.2 Creating a Firestore instance

To create a Firestore instance, use the following Kotlin code:
val firestore = FirebaseFirestore.getInstance()


3.3 Creating collections and documents using Kotlin code

You can create collections and documents using the following Kotlin code:
// Creating a new collection
val coursesCollection = firestore.collection("Courses")

// Creating a new document
val courseDocument = coursesCollection.document("Course1")


3.4 Example: Adding a collection for "Courses" and a document for each course

To add a collection for "Courses" and a document for each course, use this Kotlin code:
data class Course(val title: String, val instructor: String, val duration: Int)

fun addCourse(course: Course) {
val coursesCollection = firestore.collection("Courses")
val courseDocument = coursesCollection.document()
courseDocument.set(course)
}


Section 4: CRUD Operations with Android Kotlin Code


4.1 Introduction to CRUD operations in Firebase
CRUD operations (Create, Read, Update, Delete) are essential for managing data in Firebase. This section demonstrates examples of CRUD operations using Kotlin code.
4.2 Reading data using Kotlin code
To read data from Firestore, use the following Kotlin code:
fun getCourses(callback: (List<Course>) -> Unit) {
firestore.collection("Courses").get()
.addOnSuccessListener { documents ->
val courses = documents.map { it.toObject<Course>() }
callback(courses)
}
}


4.3 Example: Retrieving a list of courses from the "Courses" collection

getCourses { courses ->
courses.forEach { course ->
Log.d("Courses", "Course Title: ${course.title}, Instructor: ${course.instructor}, Duration: ${course.duration}")
}
}


4.4 Creating data using Kotlin code
Refer to the addCourse function in section 3.4 for creating data.

4.5 Example: Adding a new course to the "Courses" collection
val newCourse = Course("Android Development", "John Doe", 30)
addCourse(newCourse)


4.6 Updating data using Kotlin code
To update data in Firestore, use the following Kotlin code:

fun updateCourse(courseId: String, updatedCourse: Course) {
firestore.collection("Courses").document(courseId).set(updatedCourse)
}


4.7 Example: Updating a course's details in the "Courses" collection

val updatedCourse = Course("Advanced Android Development", "Jane Smith", 45)
val courseId = "Course1"
updateCourse(courseId, updatedCourse)


4.8 Deleting data using Kotlin code
To delete data from Firestore, use the following Kotlin code:

fun deleteCourse(courseId: String) {
firestore.collection("Courses").document(courseId).delete()
}

4.9 Example: Removing a course from the "Courses" collection

val courseIdToDelete = "Course1"
deleteCourse(courseIdToDelete)

Conclusion:

You now have a solid understanding of data persistence using Firebase in Android with Kotlin development. You are able to implement Android Jetpack Architecture Components, create documents and collections using Kotlin code, and perform CRUD operations with ease.
This knowledge will enable you to create robust, maintainable, and efficient Android applications with Firebase integration. Happy coding!

Lab Book 2: The complete code

Here is a complete lab for a college enrollment system using Firebase Firestore with Kotlin.

Lab: College Enrollment System with Firebase Firestore

In this lab, you will create a college enrollment system using Firebase Firestore with Kotlin.
The system will consist of three collections: Students, Classes, and Enrollments. Each collection will have its own set of documents, and you will use Kotlin code to create, read, update, and delete data from the collections.

Part 1: Setting up Firebase in your Android project

Create a new Android Studio project with an Empty Activity.
In the Firebase console, create a new project and follow the instructions to add Firebase to your Android app.
Add the following dependencies to your module-level build.gradle file:

dependencies { // Firebase Firestore and Auth implementation 'com.google.firebase:firebase-firestore-ktx:23.0.0' implementation 'com.google.firebase:firebase-auth-ktx:21.0.1' }
In your activity or fragment, initialize Firebase:

// Firebase private lateinit var firestore: FirebaseFirestore private lateinit var auth: FirebaseAuth override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // Firebase firestore = FirebaseFirestore.getInstance() auth = FirebaseAuth.getInstance() }

Part 2: Creating collections and documents

Create three collections for Students, Classes, and Enrollments:
javaCopy code
// Creating collections val studentsCollectionRef = firestore.collection("students") val classesCollectionRef = firestore.collection("classes") val enrollmentsCollectionRef = firestore.collection("enrollments")
Create documents for each collection:

// Creating documents val studentDocumentRef = studentsCollectionRef.document("studentId") val classDocumentRef = classesCollectionRef.document("classId") val enrollmentDocumentRef = enrollmentsCollectionRef.document("enrollmentId")

Part 3: Adding data to documents

Create data classes for Student, Class, and Enrollment:

data class Student(val name: String, val email: String) data class Class(val name: String, val instructor: String) data class Enrollment(val studentId: String, val classId: String)
Add data to the documents:
kotlinCopy code
// Adding data to documents val student = Student("John Doe", "johndoe@example.com") val clazz = Class("Android Development", "Jane Smith") val enrollment = Enrollment("studentId", "classId") studentDocumentRef.set(student) classDocumentRef.set(clazz) enrollmentDocumentRef.set(enrollment)

Part 4: Querying data

Query for all students:
kotlinCopy code
// Querying for all students studentsCollectionRef.get() .addOnSuccessListener { documents -> for (document in documents) { val student = document.toObject(Student::class.java) Log.d(TAG, "${document.id} => ${student.name}") } } .addOnFailureListener { exception -> Log.d(TAG, "Error getting documents: ", exception) }
Query for all classes:

// Querying for all classes classesCollectionRef.get() .addOnSuccessListener { documents -> for (document in documents) { val clazz = document.toObject(Class::class.java) Log.d(TAG, "${document.id} => ${clazz.name}") } } .addOnFailureListener { exception -> Log.d(TAG, "Error

Query for all classes:
kotlinCopy code
// Querying for all classes classesCollectionRef.get() .addOnSuccessListener { documents -> for (document in documents) { val clazz = document.toObject(Class::class.java) Log.d(TAG, "${document.id} => ${clazz.name}") } } .addOnFailureListener { exception -> Log.d(TAG, "Error getting documents: ", exception) }
Query for all enrollments:
kotlinCopy code
// Querying for all enrollments enrollmentsCollectionRef.get() .addOnSuccessListener { documents -> for (document in documents) { val enrollment = document.toObject(Enrollment::class.java) Log.d(TAG, "${document.id} => ${enrollment.studentId} enrolled in ${enrollment.classId}") } } .addOnFailureListener { exception -> Log.d(TAG, "Error getting documents: ", exception) }

Part 5: Updating data

Update student data:
kotlinCopy code
// Updating student data val updatedStudent = Student("John Doe Jr.", "johndoe@example.com") studentDocumentRef.set(updatedStudent, SetOptions.merge()) .addOnSuccessListener { Log.d(TAG, "Student data updated successfully!") } .addOnFailureListener { e -> Log.w(TAG, "Error updating student data", e) }
Update class data:
kotlinCopy code
// Updating class data val updatedClass = Class("Android Development with Kotlin", "Jane Smith") classDocumentRef.set(updatedClass, SetOptions.merge()) .addOnSuccessListener { Log.d(TAG, "Class data updated successfully!") } .addOnFailureListener { e -> Log.w(TAG, "Error updating class data", e) }

Part 6: Deleting data

Delete a student document:
goCopy code
// Deleting a student document studentDocumentRef.delete() .addOnSuccessListener { Log.d(TAG, "Student document deleted successfully!") } .addOnFailureListener { e -> Log.w(TAG, "Error deleting student document", e) }
Delete a class document:
goCopy code
// Deleting a class document classDocumentRef.delete() .addOnSuccessListener { Log.d(TAG, "Class document deleted successfully!") } .addOnFailureListener { e -> Log.w(TAG, "Error deleting class document", e) }

Part 7: Adding enrollments

Create a new enrollment document and link it to a student and class:
kotlinCopy code
// Linking a student and a class to an enrollment document val enrollmentId = enrollmentsCollectionRef.document().id val newEnrollment = Enrollment("newStudentId", "newClassId") enrollmentsCollectionRef.document(enrollmentId).set(newEnrollment) .addOnSuccessListener { Log.d(TAG, "Enrollment document added successfully!") } .addOnFailureListener { e -> Log.w(TAG, "Error adding enrollment document", e) } // Linking the enrollment to the student and class documents studentDocumentRef.update("enrollments", FieldValue.arrayUnion(enrollmentId)) classDocumentRef.update("enrollments", FieldValue.arrayUnion(enrollmentId))
Query for all enrollments:

// Querying for all enrollments enrollmentsCollectionRef.get() .addOnSuccessListener { documents -> for (document in documents) { val enrollment = document.toObject(Enrollment::class.java) Log.d(TAG, "${document.id}

Query for all enrollments:
// Querying for all enrollments enrollmentsCollectionRef.get() .addOnSuccessListener { documents -> for (document in documents) { val enrollment = document.toObject(Enrollment::class.java) Log.d(TAG, "${document.id} => ${enrollment.studentId} enrolled in ${enrollment.classId}") } } .addOnFailureListener { exception -> Log.d(TAG, "Error getting documents: ", exception) }
Congratulations! You have completed the lab for the college enrollment system using Firebase Firestore with Kotlin. You can now use the knowledge you gained to create more complex systems with Firebase.



Write a KOTLIN program to access Firebase and create a Document.

To access Firebase and create a document using Kotlin, you'll need to follow these steps:
Set up a Firebase project and enable Cloud Firestore:
a. Go to the Firebase Console () and sign in with your Google account.
done: b. Click on "Add project" and follow the on-screen instructions to create a new Firebase project.
c. Once your project is created, click on "Cloud Firestore" in the left-hand menu.
d. Click on "Create database" and choose "Start in production mode" or "Start in test mode" based on your needs.
e. Set a Cloud Firestore location for your project and click on "Enable".
Add Firebase to your Android project:
a. In the Firebase Console, click on the Android icon in the project overview page.
b. Enter your Android package name and follow the instructions to download the google-services.json file.
c. Add the Firebase SDK to your app-level build.gradle file:

dependencies { // ... implementation platform('com.google.firebase:firebase-bom:28.4.2') implementation 'com.google.firebase:firebase-firestore-ktx' }
d. Add the Google services plugin to your project-level build.gradle file:
scssCopy code
buildscript { repositories { // ... google() } dependencies { // ... classpath 'com.google.gms:google-services:4.3.10' } }
e. Finally, apply the Google services plugin in your app-level build.gradle file by adding the following line at the end of the file:
apply plugin: 'com.google.gms.google-services'
Write a Kotlin program to create a document in Firebase:
In your Kotlin code (e.g., MainActivity.kt), you can write a function to create a document in Cloud Firestore like this:

import androidx.appcompat.app.AppCompatActivity
Want to print your doc?
This is not the way.
Try clicking the ⋯ next to your doc name or using a keyboard shortcut (
CtrlP
) instead.