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
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
}
}
}
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).
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 (