ABOUT THE PROJECT
I was making a app using Kotlin, Jetpack Compose and RoomDB in RoomDB. It is a very simple app, that consist of two screens. First-> Register screen -> Using OutlinedTextField it takes the name from the user, the "Add" button add that name to the list. And then if every desired name got entered, the "Final Add List to database" add that list to the dataBase.
Second-> Profile Screen -> It shows the list to the user. I had used MVVM architecture to make this project. I had used MutableLiveData() and LiveData() in the ViewModel class to follow a good practice, so that the variable which stores all list must only be updated in viewModel and I will show the LiveData version to the UI.
DETAILS ABOUT THE PROBLEM
When I took input from the user and saved it in database. When I visited the Profile screen to see the list, the screen was just blank nothing was written on the screen neither the fetched data nor the else statement which I had given for the condition if the list is empty or null "Can't fetch...."(You will get more detail about it in my ProfileScreen.kt file).
Short Summary
Since I am new to app development or coding in general. I asked ChatGPT to resolve this problem. But it had also confused me. If anybody of you have the answer to this problem, I humbly request you to explain me in very simple words.
Register.kt
package com.example.dataset2
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.Button
import androidx.compose.material.OutlinedTextField
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
@Composable
fun RegisterScreen(
viewModel: UserViewModel = viewModel(),
onNavigateToProfileScreen: ()->Unit
){
var name by remember {
mutableStateOf("")
}
var nameList by remember {
mutableStateOf(mutableListOf<String>())
}
LazyColumn(
modifier = Modifier
.fillMaxSize()
.padding(16.dp)
){
item {
OutlinedTextField(
value = name,
onValueChange = {
name = it
},
label = { Text(text = "Enter names:")}
)
}
item {
Spacer(modifier = Modifier.height(16.dp))
}
item {
Column(
modifier = Modifier.padding(16.dp)
) {
if (nameList.isNotEmpty()){
nameList.forEach {
name_in_list ->
Text(text = name_in_list)
}
}
}
}
item {
Spacer(modifier = Modifier.height(16.dp))
}
item {
Button(onClick = {
viewModel.addList(nameList)
nameList = mutableListOf() // Clear the nameList
name= ""
}) {
Text(text = "Add")
}
}
item {
Spacer(modifier = Modifier.height(16.dp))
}
item {
Button(onClick = {
viewModel.saveListsToDatabase()
onNavigateToProfileScreen()
}) {
Text(text = "Final Add List to database")
}
}
}
}
ProfileScreen.kt
package com.example.dataset2
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
@Composable
fun ProfileScreen(viewModel: UserViewModel = viewModel()){
val userDetail by viewModel.userDetail.observeAsState(initial = emptyList())
LaunchedEffect(viewModel) {
viewModel.fetchUserDetails()
}
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
if (userDetail.isNotEmpty()){
userDetail.forEach {
user->
user.list.forEach {
Text(text = it)
}
}
}
else{
Text(text = "Can't fetch ....")
}
}
}
ViewModel
package com.example.dataset2
import androidx.compose.runtime.State
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.launch
class UserViewModel(private val userRepository: UserRepository = Graph.userRepository) : ViewModel() {
private val _userDetail = MutableLiveData<List<UserDetail>>()
val userDetail: LiveData<List<UserDetail>> get() = _userDetail
private val _allLists = mutableListOf<List<String>>()
// Loading state
private val _loading = mutableStateOf(false)
val loading: State<Boolean> get() = _loading
fun addList(list: List<String>) {
_allLists.add(list)
}
fun saveListsToDatabase() {
viewModelScope.launch {
_loading.value = true // Set loading to true before saving
for (list in _allLists) {
userRepository.insertUser(UserDetail(list = list))
}
_allLists.clear()
_loading.value = false // Set loading to false after saving
}
}
fun fetchUserDetails() {
viewModelScope.launch {
_loading.value = true // Set loading to true before fetching
userRepository.getUser().collect {
_userDetail.value = it
_loading.value = false // Set loading to false after fetching
}
}
}
}
Repository
package com.example.dataset2
import kotlinx.coroutines.flow.Flow
class UserRepository(private val userDetailDao: UserDetailDao) {
suspend fun insertUser(userDetail: UserDetail) {
userDetailDao.insertUser(userDetail)
}
fun getUser(): Flow<List<UserDetail>> {
return userDetailDao.getUser()
}
}
DataClass
package com.example.dataset2
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity
data class UserDetail(
@PrimaryKey(autoGenerate = true)
val id:Long = 0L,
val list: List<String> = emptyList()
)
Converter file, that converts list into string and then from string to list.
package com.example.dataset2
import androidx.room.TypeConverter
class Converters {
@TypeConverter
fun fromStringList(value: List<String>?):String?{
return value?.joinToString { "," }
}
@TypeConverter
fun toStringList(value: String?):List<String>?{
return value?.split(",")?.map { it.trim() }
}
}
dataBase
package com.example.dataset2
import androidx.room.Database
import androidx.room.RoomDatabase
import androidx.room.TypeConverters
@Database(
entities = [UserDetail::class],
version = 1
)
@TypeConverters(Converters::class)
abstract class Database : RoomDatabase(){
abstract fun userDetailDao(): UserDetailDao
}
Dao
package com.example.dataset2
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import kotlinx.coroutines.flow.Flow
@Dao
interface UserDetailDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertUser(userDetail: UserDetail)
@Query("Select * from userdetail")
fun getUser(): Flow<List<UserDetail>>
}
Singleton file that stores an instance of Repository and database
package com.example.dataset2
import android.content.Context
import androidx.room.Room
object Graph {
lateinit var database: Database
val userRepository by lazy {
UserRepository(database.userDetailDao())
}
fun init(context: Context){
database = Room.databaseBuilder(context,Database::class.java,"database.db").build()
}
}
Navigation and MainActivity.kt
package com.example.dataset2
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import com.example.dataset2.ui.theme.Dataset2Theme
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
Dataset2Theme {
// A surface container using the 'background' color from the theme
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
AppNavigation()
}
}
}
}
}
@Composable
fun AppNavigation(){
val navController = rememberNavController()
NavHost(navController = navController, startDestination = "registerscreen"){
composable("registerScreen"){
RegisterScreen (onNavigateToProfileScreen = {navController.navigate("profilescreen")})
}
composable("profilescreen"){
ProfileScreen()
}
}
}
Application file where I had given context to make database
package com.example.dataset2
import android.app.Application
class MyApp: Application() {
override fun onCreate() {
super.onCreate()
Graph.init(this)
}
}
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<application
android:name=".MyApp"
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.Dataset2"
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:exported="true"
android:label="@string/app_name"
android:theme="@style/Theme.Dataset2">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
Used dependencies and plugins
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
id("kotlin-parcelize")
id("kotlin-kapt")
}
android {
namespace = "com.example.dataset2"
compileSdk = 34
defaultConfig {
applicationId = "com.example.dataset2"
minSdk = 24
targetSdk = 34
versionCode = 1
versionName = "1.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
useSupportLibrary = true
}
}
buildTypes {
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = "1.8"
}
buildFeatures {
compose = true
}
composeOptions {
kotlinCompilerExtensionVersion = "1.5.1"
}
packaging {
resources {
excludes += "/META-INF/{AL2.0,LGPL2.1}"
}
}
}
dependencies {
val room = "2.6.1"
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.4.0-alpha01")
implementation("androidx.lifecycle:lifecycle-viewmodel:2.4.0-alpha01")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2")
implementation ("androidx.lifecycle:lifecycle-runtime-ktx:2.4.0")
implementation ("androidx.lifecycle:lifecycle-livedata-core-ktx:2.3.1")
//Dependencies for RoomDB
implementation("androidx.room:room-runtime:$room")
implementation("androidx.room:room-ktx:$room")
kapt("androidx.room:room-compiler:$room")
// Add the latest version of Jetpack Compose
implementation("androidx.compose.ui:ui:1.6.0")
implementation("androidx.activity:activity-compose:1.8.2")
implementation("androidx.compose.foundation:foundation:1.6.0")
implementation("androidx.compose.material:material:1.6.0")
implementation ("androidx.compose.material3:material3:1.1.2")
implementation("androidx.compose.runtime:runtime-livedata:1.6.0")
implementation("androidx.compose.runtime:runtime:1.6.0")
//Compose ViewModel
// implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.7.0")
//Network Call
implementation("com.squareup.retrofit2:retrofit:2.9.0")
//Json to Kotlin object mapping
implementation("com.squareup.retrofit2:converter-gson:2.9.0")
//Image loading
implementation("io.coil-kt:coil-compose:2.4.0")
//Navigation
implementation("androidx.navigation:navigation-compose:2.7.6")
implementation("androidx.core:core-ktx:1.12.0")
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.7.0")
implementation("androidx.activity:activity-compose:1.8.2")
implementation(platform("androidx.compose:compose-bom:2023.08.00"))
implementation("androidx.compose.ui:ui")
implementation("androidx.compose.ui:ui-graphics")
implementation("androidx.compose.ui:ui-tooling-preview")
implementation("androidx.compose.material3:material3")
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.5")
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
androidTestImplementation(platform("androidx.compose:compose-bom:2023.08.00"))
androidTestImplementation("androidx.compose.ui:ui-test-junit4")
debugImplementation("androidx.compose.ui:ui-tooling")
debugImplementation("androidx.compose.ui:ui-test-manifest")
}
I am using Android studio's hedgehog RC1. These are the things I had tried.