Share
Explore

Android App Healthy Shopper

Review a reference of basic ROOM concepts:

Lab : Create the HealthyShopper app.

Here's a breakdown of the steps involved, along with the code:
1. Project setup
Create a new Android Studio project. Choose "Empty Activity" and Kotlin as the language. Name the application "HealthyShopper". Select the minimum SDK to your preference.
2. Add dependencies
Add the following dependencies to your build.gradle (Module level):
Gradle ​dependencies { // Room components implementation("androidx.room:room-runtime:2.5.2") annotationProcessor("androidx.room:room-compiler:2.5.2") androidTestImplementation("androidx.room:room-testing:2.5.2") implementation("androidx.room:room-ktx:2.5.2")     // Kotlin Extensions and Coroutines support for Room
// Lifecycle components implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.2")   implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.6.2") implementation("androidx.lifecycle:lifecycle-common-java8:2.6.2")  
// Kotlin components implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.1")  
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.1")  
}

3. Create the data model (Entity)

Create a Kotlin data class named GroceryItem to represent the items in your shopping list.
package com.example.healthyshopper // Replace with your package name
import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.PrimaryKey
@Entity(tableName = "grocery_items") data class GroceryItem(  
@PrimaryKey(autoGenerate = true) val id:   Int = 0, @ColumnInfo(name = "item_name") val itemName: String )
4. Create the DAO (Data Access Object)
Create an interface named GroceryItemDao to define the database operations.
package com.example.healthyshopper
import androidx.room.* import kotlinx.coroutines.flow.Flow
@Dao interface GroceryItemDao {
@Query("SELECT * FROM grocery_items") fun getAllGroceryItems(): Flow<List<GroceryItem>>
@Insert suspend fun insert(groceryItem: GroceryItem)
@Update suspend fun update(groceryItem: GroceryItem)
@Delete suspend fun delete(groceryItem: GroceryItem) }
5. Create the Room database
Create an abstract class that extends RoomDatabase to define your database.
Kotlin package com.example.healthyshopper
import android.content.Context import androidx.room.Database import androidx.room.Room import androidx.room.RoomDatabase
@Database(entities = [GroceryItem::class], version = 1, exportSchema = false) abstract class GroceryItemDatabase : RoomDatabase() {
abstract fun groceryItemDao(): GroceryItemDao
companion   object { @Volatile private var INSTANCE: GroceryItemDatabase? = null
fun getDatabase(context: Context): GroceryItemDatabase { return INSTANCE ?: synchronized(this) { val instance = Room.databaseBuilder( context.applicationContext, GroceryItemDatabase::class.java, "grocery_item_database"  
).build() INSTANCE = instance instance } } } }

6. Design the layout (activity_main.xml)
XML <?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"  
xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" android:padding="16dp">
<EditText android:id="@+id/et_grocery_item"  
android:layout_width="0dp" android:layout_height="wrap_content" android:hint="Enter grocery item" app:layout_constraintTop_toTopOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toStartOf="@id/btn_add_item" android:importantForAutofill="no" />
<Button android:id="@+id/btn_add_item" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Add" app:layout_constraintTop_toTopOf="parent"  
app:layout_constraintEnd_toEndOf="parent"   />
<TextView android:id="@+id/tv_grocery_list" android:layout_width="0dp" android:layout_height="0dp" android:layout_marginTop="16dp" app:layout_constraintTop_toBottomOf="@id/et_grocery_item"  
app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintBottom_toBottomOf="parent"   />
</androidx.constraintlayout.widget.ConstraintLayout>
7. Implement the MainActivity
Kotlin package com.example.healthyshopper
import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import androidx.activity.viewModels import androidx.lifecycle.lifecycleScope  
import com.example.healthyshopper.databinding.ActivityMainBinding import kotlinx.coroutines.launch
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding private   val groceryItemViewModel: GroceryItemViewModel by viewModels { GroceryItemViewModelFactory((application as GroceryItemApplication).repository) }
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityMainBinding.inflate(layoutInflater)  
setContentView(binding.root)
// Set click listener   for the Add button binding.btnAddItem.setOnClickListener { val itemName = binding.etGroceryItem.text.toString() if (itemName.isNotBlank()) { lifecycleScope.launch { groceryItemViewModel.insert(GroceryItem(itemName = itemName)) binding.etGroceryItem.text.clear() } } }
// Observe the grocery items and update the TextView groceryItemViewModel.allGroceryItems.observe(this) { items -> val groceryList = items.joinToString("\n") { it.itemName } binding.tvGroceryList.text = groceryList } } }
8. Create the GroceryItemViewModel
Kotlin package com.example.healthyshopper
import androidx.lifecycle.* import kotlinx.coroutines.launch
class GroceryItemViewModel(private val repository: GroceryItemRepository) : ViewModel() {
val allGroceryItems: LiveData<List<GroceryItem>> = repository.allGroceryItems.asLiveData()
fun insert(groceryItem: GroceryItem) = viewModelScope.launch { repository.insert(groceryItem) }
// Add other ViewModel functions for update and delete as needed }
class GroceryItemViewModelFactory(private val repository: GroceryItemRepository) : ViewModelProvider.Factory { override fun <T : ViewModel> create(modelClass: Class<T>): T { if (modelClass.isAssignableFrom(GroceryItemViewModel::class.java)) { @Suppress("UNCHECKED_CAST") return GroceryItemViewModel(repository) as T } throw IllegalArgumentException("Unknown ViewModel class") } }
9. Create the GroceryItemRepository
Kotlin package com.example.healthyshopper
import androidx.annotation.WorkerThread
class GroceryItemRepository(private val groceryItemDao: GroceryItemDao) {
val allGroceryItems: Flow<List<GroceryItem>> = groceryItemDao.getAllGroceryItems()
@Suppress("RedundantSuspendModifier") @WorkerThread suspend fun insert(groceryItem: GroceryItem) { groceryItemDao.insert(groceryItem) }
// Add other repository functions for update and delete as needed } Use code with caution.
10. Create the GroceryItemApplication
Kotlin package com.example.healthyshopper
import android.app.Application
class GroceryItemApplication : Application() { val database by lazy { GroceryItemDatabase.getDatabase(this) } val repository by lazy { GroceryItemRepository(database.groceryItemDao()) } }
11. Update the AndroidManifest.xml
Add the GroceryItemApplication class to the AndroidManifest.xml`
XML <application ... android:name=".GroceryItemApplication" ...> ... </application>
This setup uses View Binding, Kotlin coroutines for database operations, and follows the recommended Android architecture with a ViewModel and Repository.
Explanation:
Room Database: Provides an abstraction layer over SQLite to allow easier database access while harnessing the full power of SQLite.
Entities: Represent tables in your database. In this case, GroceryItem is your entity. DAOs: Data Access Objects that define the methods for accessing your data (insert, delete, update, query).
Repository: A class that abstracts access to multiple data sources (in this case, just the Room database).
The ViewModel interacts with the Repository.
ViewModel: Holds and manages UI-related data in a lifecycle-conscious way. It uses the Repository to access and modify the data.
View Binding: A feature that allows you to more easily write code that interacts with views, replacing findViewById.
Coroutines: A concurrency design pattern that helps you simplify asynchronous code (like database operations).


info

While the code I provided is structurally sound, there are some potential issues and areas for improvement:

1. Error Handling
Database Operations: Database operations (insert, update, delete) can fail. The code should include error handling (e.g., try-catch blocks) to gracefully handle potential exceptions like database constraint violations or I/O errors.
Input Validation: The code currently only checks if the input is blank. It should be more robust and handle cases like invalid characters, excessively long input, etc. This prevents unexpected behavior and database issues.
2. UI Thread Blocking
Long-running Operations: Database operations should ideally be performed off the main thread to avoid blocking the UI and causing ANRs (Application Not Responding). You can use coroutines with Dispatchers.IO for this.
3. Efficiency
Database Queries: For larger datasets, fetching all items every time the data changes might become inefficient. Consider using more specific queries or pagination if necessary.
4. Testability
ViewModel Dependency: The GroceryItemViewModel directly depends on the GroceryItemRepository. This can make unit testing difficult. Consider using dependency injection (e.g., Hilt) to provide the repository as a dependency, allowing you to easily mock it in tests.

MainActivity: The implementation looks good, but ensure you've imported the necessary libraries and that the GroceryItemApplication class is correctly referenced.
AndroidManifest.xml: Ensure the GroceryItemApplication class is correctly registered.

Improved Code (with some of the suggestions above):
Improved Code (with some of the suggestions above):
Kotlin // MainActivity.kt binding.btnAddItem.setOnClickListener { val itemName = binding.etGroceryItem.text.toString() if (itemName.isNotBlank()) { lifecycleScope.launch { try { groceryItemViewModel.insert(GroceryItem(itemName = itemName)) binding.etGroceryItem.text.clear() } catch (e: Exception) { // Handle the exception, e.g., show an error message Toast.makeText(this@MainActivity, "Error adding item", Toast.LENGTH_SHORT).show() Log.e("MainActivity", "Error inserting item", e) } } } }
// GroceryItemViewModel.kt ​fun insert(groceryItem: GroceryItem) = viewModelScope.launch(Dispatchers.IO) { try { repository.insert(groceryItem) } catch (e: Exception) { // Handle the exception, e.g., post an error event to the UI } }
This improved code includes basic error handling for database inserts and moves the database operation to a background thread.
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.