From 2649a57c0037233ab75f2461619796d2420ffae7 Mon Sep 17 00:00:00 2001 From: Jeff Lockhart Date: Fri, 9 Dec 2022 17:52:43 -0700 Subject: [PATCH 1/9] Observe authenticated user to update profile --- .../services/AuthenticationService.kt | 2 + .../services/MockAuthenticationService.kt | 14 ++-- .../couchbase/learningpath/ui/MainActivity.kt | 40 +++++----- .../ui/profile/UserProfileView.kt | 25 ++++-- .../ui/profile/UserProfileViewModel.kt | 76 +++++++++++-------- 5 files changed, 93 insertions(+), 64 deletions(-) diff --git a/src/app/src/main/java/com/couchbase/learningpath/services/AuthenticationService.kt b/src/app/src/main/java/com/couchbase/learningpath/services/AuthenticationService.kt index 9dda337..7a52be4 100644 --- a/src/app/src/main/java/com/couchbase/learningpath/services/AuthenticationService.kt +++ b/src/app/src/main/java/com/couchbase/learningpath/services/AuthenticationService.kt @@ -1,8 +1,10 @@ package com.couchbase.learningpath.services +import androidx.lifecycle.LiveData import com.couchbase.learningpath.models.User interface AuthenticationService { + val currentUser: LiveData fun getCurrentUser() : User fun authenticatedUser(username: String, password: String) : Boolean fun logout() diff --git a/src/app/src/main/java/com/couchbase/learningpath/services/MockAuthenticationService.kt b/src/app/src/main/java/com/couchbase/learningpath/services/MockAuthenticationService.kt index 4d0b5cc..4f41071 100644 --- a/src/app/src/main/java/com/couchbase/learningpath/services/MockAuthenticationService.kt +++ b/src/app/src/main/java/com/couchbase/learningpath/services/MockAuthenticationService.kt @@ -1,33 +1,37 @@ package com.couchbase.learningpath.services +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData import com.couchbase.learningpath.models.User class MockAuthenticationService : AuthenticationService { - private var _user: User? = null + private var _user = MutableLiveData() private var _mockUsers = HashMap() + override val currentUser: LiveData = _user + override fun getCurrentUser(): User { - return _user?: User("", "", "") + return _user.value ?: User("", "", "") } override fun authenticatedUser(username: String, password: String): Boolean { return if (_mockUsers.containsKey(username)){ val user = _mockUsers[username] if (user?.password == password){ - _user = user + _user.value = user true } else { false } } else { - _user = User(username = username, password = password, team = "team2") + _user.value = User(username = username, password = password, team = "team2") return true } } override fun logout() { - _user = null + _user.value = null } init { diff --git a/src/app/src/main/java/com/couchbase/learningpath/ui/MainActivity.kt b/src/app/src/main/java/com/couchbase/learningpath/ui/MainActivity.kt index 7007ee0..fce201c 100644 --- a/src/app/src/main/java/com/couchbase/learningpath/ui/MainActivity.kt +++ b/src/app/src/main/java/com/couchbase/learningpath/ui/MainActivity.kt @@ -6,6 +6,7 @@ import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.material.* import androidx.compose.runtime.* +import androidx.compose.runtime.livedata.observeAsState import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.res.stringResource @@ -24,18 +25,22 @@ import org.koin.androidx.compose.getViewModel import com.couchbase.learningpath.InventoryNavGraph import com.couchbase.learningpath.MainDestinations import com.couchbase.learningpath.R -import com.couchbase.learningpath.data.KeyValueRepository import com.couchbase.learningpath.services.AuthenticationService import com.couchbase.learningpath.services.ReplicatorService import com.couchbase.learningpath.ui.components.Drawer import com.couchbase.learningpath.ui.profile.UserProfileViewModel import com.couchbase.learningpath.ui.theme.LearningPathTheme import kotlinx.coroutines.ExperimentalCoroutinesApi +import org.koin.androidx.viewmodel.ext.android.viewModel @kotlinx.serialization.ExperimentalSerializationApi @ExperimentalMaterialApi @ExperimentalCoroutinesApi class MainActivity : ComponentActivity() { + + //used for drawing profile in drawer + private val profileViewModel: UserProfileViewModel by viewModel() + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -45,18 +50,13 @@ class MainActivity : ComponentActivity() { val navController = rememberNavController() val scaffoldState = rememberScaffoldState() val authService: AuthenticationService by inject() - val userProfileRepository: KeyValueRepository by inject() val replicatorService : ReplicatorService by inject() val menuResource = stringResource(id = R.string.btnMenu) val mainViewModel = getViewModel() - //used for drawing profile in drawer - var profileViewModel: UserProfileViewModel? = null - fun logout() { replicatorService.stopReplication() replicatorService.updateAuthentication(isReset = true) - profileViewModel = null authService.logout() } //we need a drawer overflow menu on multiple screens @@ -65,15 +65,6 @@ class MainActivity : ComponentActivity() { val drawerState = rememberDrawerState(DrawerValue.Closed) val openDrawer = { scope.launch { - if (profileViewModel == null) { - profileViewModel = UserProfileViewModel( - application = application, - repository = userProfileRepository, - authService = authService, - ) - } else { - profileViewModel?.updateUserProfileInfo() - } drawerState.open() } } @@ -84,17 +75,24 @@ class MainActivity : ComponentActivity() { scaffoldState.snackbarHostState }) { ModalDrawer( - modifier = Modifier.semantics { contentDescription = menuResource }, + modifier = Modifier + .semantics { contentDescription = menuResource }, drawerState = drawerState, gesturesEnabled = drawerState.isOpen, drawerContent = { + val givenName by profileViewModel.givenName.observeAsState() + val surname by profileViewModel.surname.observeAsState() + val emailAddress by profileViewModel.emailAddress.observeAsState() + val team by profileViewModel.team.observeAsState() + val profilePic by profileViewModel.profilePic.observeAsState() + Drawer( modifier = Modifier.semantics { contentDescription = "{$menuResource}1" }, - firstName = profileViewModel?.givenName?.value, - lastName = profileViewModel?.surname?.value, - email = profileViewModel?.emailAddress?.value, - team = profileViewModel?.team?.value, - profilePicture = profileViewModel?.profilePic?.value, + firstName = givenName, + lastName = surname, + email = emailAddress, + team = team, + profilePicture = profilePic, onClicked = { route -> scope.launch { drawerState.close() diff --git a/src/app/src/main/java/com/couchbase/learningpath/ui/profile/UserProfileView.kt b/src/app/src/main/java/com/couchbase/learningpath/ui/profile/UserProfileView.kt index ddb9813..be40597 100644 --- a/src/app/src/main/java/com/couchbase/learningpath/ui/profile/UserProfileView.kt +++ b/src/app/src/main/java/com/couchbase/learningpath/ui/profile/UserProfileView.kt @@ -18,6 +18,7 @@ import androidx.compose.material.* import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Menu import androidx.compose.runtime.* +import androidx.compose.runtime.livedata.observeAsState import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip @@ -42,7 +43,15 @@ import com.couchbase.learningpath.ui.theme.Red500 fun UserProfileView( openDrawer: () -> Unit, scaffoldState: ScaffoldState = rememberScaffoldState(), - viewModel: UserProfileViewModel) { + viewModel: UserProfileViewModel +) { + val givenName by viewModel.givenName.observeAsState("") + val surname by viewModel.surname.observeAsState("") + val jobTitle by viewModel.jobTitle.observeAsState("") + val profilePic by viewModel.profilePic.observeAsState() + val emailAddress by viewModel.emailAddress.observeAsState("") + val team by viewModel.team.observeAsState("") + val toastMessage by viewModel.toastMessage.observeAsState() LearningPathTheme { // A surface container using the 'background' color from the theme @@ -59,17 +68,17 @@ fun UserProfileView( ) { UserProfileFormWidget( - viewModel.givenName.value, + givenName, viewModel.onGivenNameChanged, - viewModel.surname.value, + surname, viewModel.onSurnameChanged, - viewModel.jobTitle.value, + jobTitle, viewModel.onJobTitleChanged, - viewModel.profilePic.value, + profilePic, viewModel.onProfilePicChanged, - viewModel.emailAddress.value, - viewModel.team.value, - viewModel.toastMessage.value, + emailAddress, + team, + toastMessage, viewModel.onSave, viewModel.clearToastMessage ) diff --git a/src/app/src/main/java/com/couchbase/learningpath/ui/profile/UserProfileViewModel.kt b/src/app/src/main/java/com/couchbase/learningpath/ui/profile/UserProfileViewModel.kt index ab3b83b..5063fb2 100644 --- a/src/app/src/main/java/com/couchbase/learningpath/ui/profile/UserProfileViewModel.kt +++ b/src/app/src/main/java/com/couchbase/learningpath/ui/profile/UserProfileViewModel.kt @@ -4,8 +4,10 @@ import android.app.Application import android.graphics.Bitmap import android.graphics.BitmapFactory import android.graphics.drawable.Drawable -import androidx.compose.runtime.mutableStateOf import androidx.core.graphics.drawable.toBitmap +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.Observer import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.viewModelScope import com.couchbase.lite.Blob @@ -22,30 +24,35 @@ import com.couchbase.learningpath.models.User class UserProfileViewModel( application: Application, private val repository: KeyValueRepository, - authService: AuthenticationService + private val authService: AuthenticationService ) : AndroidViewModel(application) { - private var currentUser: User? = null - //track our fields in our composable - var givenName = mutableStateOf("") - var surname = mutableStateOf("") - var jobTitle = mutableStateOf("") - var emailAddress = mutableStateOf("") - var team = mutableStateOf("") - var toastMessage = mutableStateOf("") - var profilePic = mutableStateOf(null) + private val _givenName = MutableLiveData("") + val givenName: LiveData = _givenName - init { - currentUser = authService.getCurrentUser() - updateUserProfileInfo() - profilePic.value = BitmapFactory.decodeResource(getApplication().resources, R.drawable.profile_placeholder) - } + private val _surname = MutableLiveData("") + val surname: LiveData = _surname + + private val _jobTitle = MutableLiveData("") + val jobTitle: LiveData = _jobTitle + + private val _emailAddress = MutableLiveData("") + val emailAddress: LiveData = _emailAddress + + private val _team = MutableLiveData("") + val team: LiveData = _team + + private val _toastMessage = MutableLiveData("") + val toastMessage: LiveData = _toastMessage - fun updateUserProfileInfo() { + private val _profilePic = MutableLiveData(null) + val profilePic: LiveData = _profilePic + + private val userObserver: (User?) -> Unit = { currentUser -> currentUser?.let { authenticatedUser -> - emailAddress.value = authenticatedUser.username - team.value = authenticatedUser.team + _emailAddress.value = authenticatedUser.username + _team.value = authenticatedUser.team //when getting information from the database need to make sure //to use Dispatchers.IO so that Disk I/O work isn't done on the main thread viewModelScope.launch(Dispatchers.IO) { @@ -53,44 +60,53 @@ class UserProfileViewModel( //make sure when we update the UI we update on the Main Thread withContext(Dispatchers.Main) { userProfile["givenName"]?.let { - givenName.value = userProfile["givenName"] as String + _givenName.value = userProfile["givenName"] as String } userProfile["surname"]?.let { - surname.value = userProfile["surname"] as String + _surname.value = userProfile["surname"] as String } userProfile["jobTitle"]?.let { - jobTitle.value = userProfile["jobTitle"] as String + _jobTitle.value = userProfile["jobTitle"] as String } userProfile["imageData"]?.let { val blob = userProfile["imageData"] as Blob val d = Drawable.createFromStream(blob.contentStream, "res") - profilePic.value = d?.toBitmap() + _profilePic.value = d?.toBitmap() } } } } } + init { + authService.currentUser.observeForever(userObserver) + _profilePic.value = BitmapFactory.decodeResource(getApplication().resources, R.drawable.profile_placeholder) + } + + override fun onCleared() { + authService.currentUser.removeObserver(userObserver) + } + val onGivenNameChanged: (String) -> Unit = { newValue -> - givenName.value = newValue + _givenName.value = newValue } val onSurnameChanged: (String) -> Unit = { newValue -> - surname.value = newValue + _surname.value = newValue } val onJobTitleChanged: (String) -> Unit = { newValue -> - jobTitle.value = newValue + _jobTitle.value = newValue } val onProfilePicChanged: (Bitmap) -> Unit = { newValue -> viewModelScope.launch(Dispatchers.Main) { - profilePic.value = newValue + _profilePic.value = newValue } } val clearToastMessage: () -> Unit = { - toastMessage.value = "" + _toastMessage.value = "" } val onSave: () -> Unit = { @@ -115,9 +131,9 @@ class UserProfileViewModel( //make sure when we update the UI we update on the Main Thread withContext(Dispatchers.Main) { if (didSave) { - toastMessage.value = "Successfully updated profile" + _toastMessage.value = "Successfully updated profile" } else { - toastMessage.value = "Error saving, try again later." + _toastMessage.value = "Error saving, try again later." } } } From 1595f10d2003bdd7df984fb00b709240764f0f4b Mon Sep 17 00:00:00 2001 From: Jeff Lockhart Date: Thu, 5 Jan 2023 17:00:45 -0700 Subject: [PATCH 2/9] Fix gradle build error --- src/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/build.gradle b/src/build.gradle index 7292899..0287fcd 100644 --- a/src/build.gradle +++ b/src/build.gradle @@ -21,11 +21,11 @@ buildscript { } repositories { + google() + mavenCentral() maven { url "https://mobile.maven.couchbase.com/maven2/dev/" } - google() - mavenCentral() } dependencies { classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" From 8e82b6832f30d36b88e978d5f6af61eda1433e0b Mon Sep 17 00:00:00 2001 From: Jeff Lockhart Date: Thu, 5 Jan 2023 17:01:41 -0700 Subject: [PATCH 3/9] Use compose state instead of live data --- .../couchbase/learningpath/ui/MainActivity.kt | 17 +--- .../ui/profile/UserProfileView.kt | 26 ++--- .../ui/profile/UserProfileViewModel.kt | 99 +++++++++---------- 3 files changed, 59 insertions(+), 83 deletions(-) diff --git a/src/app/src/main/java/com/couchbase/learningpath/ui/MainActivity.kt b/src/app/src/main/java/com/couchbase/learningpath/ui/MainActivity.kt index fce201c..aae300d 100644 --- a/src/app/src/main/java/com/couchbase/learningpath/ui/MainActivity.kt +++ b/src/app/src/main/java/com/couchbase/learningpath/ui/MainActivity.kt @@ -6,7 +6,6 @@ import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.material.* import androidx.compose.runtime.* -import androidx.compose.runtime.livedata.observeAsState import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.res.stringResource @@ -80,19 +79,13 @@ class MainActivity : ComponentActivity() { drawerState = drawerState, gesturesEnabled = drawerState.isOpen, drawerContent = { - val givenName by profileViewModel.givenName.observeAsState() - val surname by profileViewModel.surname.observeAsState() - val emailAddress by profileViewModel.emailAddress.observeAsState() - val team by profileViewModel.team.observeAsState() - val profilePic by profileViewModel.profilePic.observeAsState() - Drawer( modifier = Modifier.semantics { contentDescription = "{$menuResource}1" }, - firstName = givenName, - lastName = surname, - email = emailAddress, - team = team, - profilePicture = profilePic, + firstName = profileViewModel.givenName, + lastName = profileViewModel.surname, + email = profileViewModel.emailAddress, + team = profileViewModel.team, + profilePicture = profileViewModel.profilePic, onClicked = { route -> scope.launch { drawerState.close() diff --git a/src/app/src/main/java/com/couchbase/learningpath/ui/profile/UserProfileView.kt b/src/app/src/main/java/com/couchbase/learningpath/ui/profile/UserProfileView.kt index be40597..e8525e6 100644 --- a/src/app/src/main/java/com/couchbase/learningpath/ui/profile/UserProfileView.kt +++ b/src/app/src/main/java/com/couchbase/learningpath/ui/profile/UserProfileView.kt @@ -18,7 +18,6 @@ import androidx.compose.material.* import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Menu import androidx.compose.runtime.* -import androidx.compose.runtime.livedata.observeAsState import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip @@ -45,14 +44,6 @@ fun UserProfileView( scaffoldState: ScaffoldState = rememberScaffoldState(), viewModel: UserProfileViewModel ) { - val givenName by viewModel.givenName.observeAsState("") - val surname by viewModel.surname.observeAsState("") - val jobTitle by viewModel.jobTitle.observeAsState("") - val profilePic by viewModel.profilePic.observeAsState() - val emailAddress by viewModel.emailAddress.observeAsState("") - val team by viewModel.team.observeAsState("") - val toastMessage by viewModel.toastMessage.observeAsState() - LearningPathTheme { // A surface container using the 'background' color from the theme Scaffold(scaffoldState = scaffoldState, @@ -65,20 +56,19 @@ fun UserProfileView( Surface( color = MaterialTheme.colors.background, modifier = Modifier.fillMaxSize() - ) - { + ) { UserProfileFormWidget( - givenName, + viewModel.givenName, viewModel.onGivenNameChanged, - surname, + viewModel.surname, viewModel.onSurnameChanged, - jobTitle, + viewModel.jobTitle, viewModel.onJobTitleChanged, - profilePic, + viewModel.profilePic, viewModel.onProfilePicChanged, - emailAddress, - team, - toastMessage, + viewModel.emailAddress, + viewModel.team, + viewModel.toastMessage, viewModel.onSave, viewModel.clearToastMessage ) diff --git a/src/app/src/main/java/com/couchbase/learningpath/ui/profile/UserProfileViewModel.kt b/src/app/src/main/java/com/couchbase/learningpath/ui/profile/UserProfileViewModel.kt index 5063fb2..37f8be4 100644 --- a/src/app/src/main/java/com/couchbase/learningpath/ui/profile/UserProfileViewModel.kt +++ b/src/app/src/main/java/com/couchbase/learningpath/ui/profile/UserProfileViewModel.kt @@ -4,10 +4,10 @@ import android.app.Application import android.graphics.Bitmap import android.graphics.BitmapFactory import android.graphics.drawable.Drawable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue import androidx.core.graphics.drawable.toBitmap -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.Observer import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.viewModelScope import com.couchbase.lite.Blob @@ -28,51 +28,48 @@ class UserProfileViewModel( ) : AndroidViewModel(application) { //track our fields in our composable - private val _givenName = MutableLiveData("") - val givenName: LiveData = _givenName + var givenName by mutableStateOf("") + private set - private val _surname = MutableLiveData("") - val surname: LiveData = _surname + var surname by mutableStateOf("") + private set - private val _jobTitle = MutableLiveData("") - val jobTitle: LiveData = _jobTitle + var jobTitle by mutableStateOf("") + private set - private val _emailAddress = MutableLiveData("") - val emailAddress: LiveData = _emailAddress + var emailAddress by mutableStateOf("") + private set - private val _team = MutableLiveData("") - val team: LiveData = _team + var team by mutableStateOf("") + private set - private val _toastMessage = MutableLiveData("") - val toastMessage: LiveData = _toastMessage + var toastMessage by mutableStateOf("") + private set - private val _profilePic = MutableLiveData(null) - val profilePic: LiveData = _profilePic + var profilePic by mutableStateOf(defaultProfilePic()) + private set + + private fun defaultProfilePic(): Bitmap { + return BitmapFactory.decodeResource(getApplication().resources, R.drawable.profile_placeholder) + } private val userObserver: (User?) -> Unit = { currentUser -> currentUser?.let { authenticatedUser -> - _emailAddress.value = authenticatedUser.username - _team.value = authenticatedUser.team + emailAddress = authenticatedUser.username + team = authenticatedUser.team //when getting information from the database need to make sure //to use Dispatchers.IO so that Disk I/O work isn't done on the main thread viewModelScope.launch(Dispatchers.IO) { val userProfile = repository.get(authenticatedUser.username) //make sure when we update the UI we update on the Main Thread withContext(Dispatchers.Main) { - userProfile["givenName"]?.let { - _givenName.value = userProfile["givenName"] as String - } - userProfile["surname"]?.let { - _surname.value = userProfile["surname"] as String - } - userProfile["jobTitle"]?.let { - _jobTitle.value = userProfile["jobTitle"] as String - } - userProfile["imageData"]?.let { - val blob = userProfile["imageData"] as Blob + givenName = userProfile["givenName"] as? String ?: "" + surname = userProfile["surname"] as? String ?: "" + jobTitle = userProfile["jobTitle"] as? String ?: "" + profilePic = (userProfile["imageData"] as? Blob)?.let { blob -> val d = Drawable.createFromStream(blob.contentStream, "res") - _profilePic.value = d?.toBitmap() - } + d?.toBitmap() + } ?: defaultProfilePic() } } } @@ -80,7 +77,6 @@ class UserProfileViewModel( init { authService.currentUser.observeForever(userObserver) - _profilePic.value = BitmapFactory.decodeResource(getApplication().resources, R.drawable.profile_placeholder) } override fun onCleared() { @@ -88,25 +84,25 @@ class UserProfileViewModel( } val onGivenNameChanged: (String) -> Unit = { newValue -> - _givenName.value = newValue + givenName = newValue } val onSurnameChanged: (String) -> Unit = { newValue -> - _surname.value = newValue + surname = newValue } val onJobTitleChanged: (String) -> Unit = { newValue -> - _jobTitle.value = newValue + jobTitle = newValue } val onProfilePicChanged: (Bitmap) -> Unit = { newValue -> viewModelScope.launch(Dispatchers.Main) { - _profilePic.value = newValue + profilePic = newValue } } val clearToastMessage: () -> Unit = { - _toastMessage.value = "" + toastMessage = "" } val onSave: () -> Unit = { @@ -114,26 +110,23 @@ class UserProfileViewModel( //to use Dispatchers.IO so that Disk I/O work isn't done on the main thread viewModelScope.launch(Dispatchers.IO) { val profile = HashMap() - profile["givenName"] = givenName.value as Any - profile["surname"] = surname.value as Any - profile["jobTitle"] = jobTitle.value as Any - profile["email"] = emailAddress.value as Any - profile["team"] = team.value as Any - profile["documentType"] = "user" as Any - profilePic.value?.let { - val outputStream = ByteArrayOutputStream() - it.compress(Bitmap.CompressFormat.JPEG, 100, outputStream) - profile["imageData"] = - Blob("image/jpeg", outputStream.toByteArray()) as Any - } + profile["givenName"] = givenName + profile["surname"] = surname + profile["jobTitle"] = jobTitle + profile["email"] = emailAddress + profile["team"] = team + profile["documentType"] = "user" + val outputStream = ByteArrayOutputStream() + profilePic.compress(Bitmap.CompressFormat.JPEG, 100, outputStream) + profile["imageData"] = Blob("image/jpeg", outputStream.toByteArray()) val didSave = repository.save(profile) //make sure when we update the UI we update on the Main Thread withContext(Dispatchers.Main) { - if (didSave) { - _toastMessage.value = "Successfully updated profile" + toastMessage = if (didSave) { + "Successfully updated profile" } else { - _toastMessage.value = "Error saving, try again later." + "Error saving, try again later." } } } From 7cb070e20631b38c05df23529ab6af7e4746e0ab Mon Sep 17 00:00:00 2001 From: Jeff Lockhart Date: Mon, 9 Jan 2023 22:28:58 -0700 Subject: [PATCH 4/9] UserProfileRepository interface KeyValueRepository is generic interface, not specific to UserProfileRepository --- .../learningpath/DatabaseIntegrationTests.kt | 6 +- .../learningpath/InventoryApplication.kt | 4 +- .../learningpath/data/KeyValueRepository.kt | 5 +- .../data/userprofile/UserProfileRepository.kt | 99 +------------------ .../userprofile/UserProfileRepositoryDb.kt | 94 ++++++++++++++++++ .../ui/developer/DevDatabaseInfoViewModel.kt | 4 +- .../ui/profile/UserProfileViewModel.kt | 4 +- 7 files changed, 108 insertions(+), 108 deletions(-) create mode 100644 src/app/src/main/java/com/couchbase/learningpath/data/userprofile/UserProfileRepositoryDb.kt diff --git a/src/app/src/androidTest/java/com/couchbase/learningpath/DatabaseIntegrationTests.kt b/src/app/src/androidTest/java/com/couchbase/learningpath/DatabaseIntegrationTests.kt index 83d6f7d..8ee419c 100644 --- a/src/app/src/androidTest/java/com/couchbase/learningpath/DatabaseIntegrationTests.kt +++ b/src/app/src/androidTest/java/com/couchbase/learningpath/DatabaseIntegrationTests.kt @@ -8,7 +8,7 @@ import com.couchbase.learningpath.data.DatabaseManager import com.couchbase.learningpath.data.audits.AuditRepositoryDb import com.couchbase.learningpath.data.project.ProjectRepositoryDb import com.couchbase.learningpath.data.stockItem.StockItemRepositoryDb -import com.couchbase.learningpath.data.userprofile.UserProfileRepository +import com.couchbase.learningpath.data.userprofile.UserProfileRepositoryDb import com.couchbase.learningpath.data.warehouse.WarehouseRepositoryDb import com.couchbase.learningpath.models.* import com.couchbase.learningpath.services.MockAuthenticationService @@ -34,7 +34,7 @@ class DatabaseIntegrationTests { private lateinit var warehouseRepository: WarehouseRepositoryDb private lateinit var auditRepository: AuditRepositoryDb private lateinit var stockItemRepository: StockItemRepositoryDb - private lateinit var userProfileRepository: UserProfileRepository + private lateinit var userProfileRepository: UserProfileRepositoryDb //test users private lateinit var user1: User @@ -87,7 +87,7 @@ class DatabaseIntegrationTests { auditRepository = AuditRepositoryDb(authenticationService, databaseManager) stockItemRepository = StockItemRepositoryDb(databaseManager) warehouseRepository = WarehouseRepositoryDb(databaseManager) - userProfileRepository = UserProfileRepository(databaseManager) + userProfileRepository = UserProfileRepositoryDb(databaseManager) projectRepository = ProjectRepositoryDb( authenticationService = authenticationService, warehouseRepository = warehouseRepository, diff --git a/src/app/src/main/java/com/couchbase/learningpath/InventoryApplication.kt b/src/app/src/main/java/com/couchbase/learningpath/InventoryApplication.kt index 3ccfdeb..6561880 100644 --- a/src/app/src/main/java/com/couchbase/learningpath/InventoryApplication.kt +++ b/src/app/src/main/java/com/couchbase/learningpath/InventoryApplication.kt @@ -10,7 +10,6 @@ import org.koin.core.logger.Level import org.koin.core.module.Module import org.koin.dsl.module -import com.couchbase.learningpath.data.KeyValueRepository import com.couchbase.learningpath.data.audits.AuditRepository import com.couchbase.learningpath.data.audits.AuditRepositoryDb import com.couchbase.learningpath.data.project.ProjectRepository @@ -18,6 +17,7 @@ import com.couchbase.learningpath.data.project.ProjectRepositoryDb import com.couchbase.learningpath.data.stockItem.StockItemRepository import com.couchbase.learningpath.data.stockItem.StockItemRepositoryDb import com.couchbase.learningpath.data.userprofile.UserProfileRepository +import com.couchbase.learningpath.data.userprofile.UserProfileRepositoryDb import com.couchbase.learningpath.data.warehouse.WarehouseRepository import com.couchbase.learningpath.data.warehouse.WarehouseRepositoryDb import com.couchbase.learningpath.services.AuthenticationService @@ -74,7 +74,7 @@ class InventoryApplication singleOf(::DatabaseManager) singleOf(::MockAuthenticationService) bind AuthenticationService::class singleOf(::ReplicatorServiceDb) bind ReplicatorService::class - singleOf(::UserProfileRepository) bind KeyValueRepository::class + singleOf(::UserProfileRepositoryDb) bind UserProfileRepository::class singleOf(::WarehouseRepositoryDb) bind WarehouseRepository::class singleOf(::ProjectRepositoryDb) bind ProjectRepository::class singleOf(::StockItemRepositoryDb) bind StockItemRepository::class diff --git a/src/app/src/main/java/com/couchbase/learningpath/data/KeyValueRepository.kt b/src/app/src/main/java/com/couchbase/learningpath/data/KeyValueRepository.kt index d7a1225..5b71246 100644 --- a/src/app/src/main/java/com/couchbase/learningpath/data/KeyValueRepository.kt +++ b/src/app/src/main/java/com/couchbase/learningpath/data/KeyValueRepository.kt @@ -2,10 +2,7 @@ package com.couchbase.learningpath.data interface KeyValueRepository { - fun inventoryDatabaseName(): String - fun inventoryDatabaseLocation(): String? - suspend fun count(): Int - suspend fun get(currentUser: String): Map + suspend fun get(key: String): Map suspend fun save(data: Map) : Boolean } \ No newline at end of file diff --git a/src/app/src/main/java/com/couchbase/learningpath/data/userprofile/UserProfileRepository.kt b/src/app/src/main/java/com/couchbase/learningpath/data/userprofile/UserProfileRepository.kt index 08628b8..a80a791 100644 --- a/src/app/src/main/java/com/couchbase/learningpath/data/userprofile/UserProfileRepository.kt +++ b/src/app/src/main/java/com/couchbase/learningpath/data/userprofile/UserProfileRepository.kt @@ -1,101 +1,10 @@ package com.couchbase.learningpath.data.userprofile -import com.couchbase.lite.CouchbaseLiteException -import com.couchbase.lite.MutableDocument - -import com.couchbase.learningpath.data.DatabaseManager import com.couchbase.learningpath.data.KeyValueRepository -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext - -class UserProfileRepository( - private val databaseManager: DatabaseManager -) : KeyValueRepository { - private val userProfileType = "user" - - override fun inventoryDatabaseName(): String { - return databaseManager.currentInventoryDatabaseName - } - - override fun inventoryDatabaseLocation(): String? { - return databaseManager.inventoryDatabase?.path - } - - override suspend fun get(currentUser: String): Map { - return withContext(Dispatchers.IO) { - val results = HashMap() // <1> - results["email"] = currentUser as Any // <2> - - val database = databaseManager.inventoryDatabase - database?.let { db -> - val documentId = getCurrentUserDocumentId(currentUser) - val doc = db.getDocument(documentId) // <3> - if (doc != null) { - if (doc.contains("givenName")) { // <4> - results["givenName"] = doc.getString("givenName") as Any // <4> - } - if (doc.contains("surname")) { // <4> - results["surname"] = doc.getString("surname") as Any // <4> - } - if (doc.contains("jobTitle")) { // <4> - results["jobTitle"] = doc.getString("jobTitle") as Any // <4> - } - if (doc.contains("team")) { // <4> - results["team"] = doc.getString("team") as Any // <4> - } - if (doc.contains("imageData")) { // <4> - results["imageData"] = doc.getBlob("imageData") as Any // <4> - } - } - } - return@withContext results // <5> - } - } - - override suspend fun save(data: Map): Boolean { - return withContext(Dispatchers.IO) { - val email = data["email"] as String - val documentId = getCurrentUserDocumentId(email) - val mutableDocument = MutableDocument(documentId, data) - try { - val database = databaseManager.inventoryDatabase - database?.save(mutableDocument) - } catch (e: CouchbaseLiteException) { - android.util.Log.e(e.message, e.stackTraceToString()) - return@withContext false - } - return@withContext true - } - } - - override suspend fun count(): Int { - return withContext(Dispatchers.IO) { - val database = databaseManager.inventoryDatabase - database?.let { db -> - val query = "SELECT COUNT(*) AS count FROM _ WHERE documentType='$userProfileType'" - val results = db.createQuery(query).execute().allResults() - return@withContext results[0].getInt("count") - } - return@withContext 0 - } - } - suspend fun delete(documentId: String): Boolean { - return withContext(Dispatchers.IO) { - var result = false - val database = databaseManager.inventoryDatabase - database?.let { db -> - val document = db.getDocument(documentId) - document?.let { - db.delete(it) - result = true - } - } - return@withContext result - } - } +interface UserProfileRepository : KeyValueRepository { - private fun getCurrentUserDocumentId(currentUser: String): String { - return "user::${currentUser}" - } + fun inventoryDatabaseName(): String + fun inventoryDatabaseLocation(): String? + suspend fun delete(documentId: String): Boolean } \ No newline at end of file diff --git a/src/app/src/main/java/com/couchbase/learningpath/data/userprofile/UserProfileRepositoryDb.kt b/src/app/src/main/java/com/couchbase/learningpath/data/userprofile/UserProfileRepositoryDb.kt new file mode 100644 index 0000000..24db492 --- /dev/null +++ b/src/app/src/main/java/com/couchbase/learningpath/data/userprofile/UserProfileRepositoryDb.kt @@ -0,0 +1,94 @@ +package com.couchbase.learningpath.data.userprofile + +import com.couchbase.lite.CouchbaseLiteException +import com.couchbase.lite.MutableDocument + +import com.couchbase.learningpath.data.DatabaseManager +import com.couchbase.lite.documentChangeFlow +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.emptyFlow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.withContext + +class UserProfileRepositoryDb( + private val databaseManager: DatabaseManager +) : UserProfileRepository { + private val userProfileType = "user" + + override fun inventoryDatabaseName(): String { + return databaseManager.currentInventoryDatabaseName + } + + override fun inventoryDatabaseLocation(): String? { + return databaseManager.inventoryDatabase?.path + } + + override suspend fun get(key: String): Map { + return withContext(Dispatchers.IO) { + val results = HashMap() // <1> + results["email"] = key // <2> + + val database = databaseManager.inventoryDatabase + database?.let { db -> + val documentId = getCurrentUserDocumentId(key) + val doc = db.getDocument(documentId) // <3> + if (doc != null) { + results["givenName"] = doc.getString("givenName") // <4> + results["surname"] = doc.getString("surname") // <4> + results["jobTitle"] = doc.getString("jobTitle") // <4> + results["team"] = doc.getString("team") // <4> + results["imageData"] = doc.getBlob("imageData") // <4> + } + } + results // <5> + } + } + + override suspend fun save(data: Map): Boolean { + return withContext(Dispatchers.IO) { + val email = data["email"] as String + val documentId = getCurrentUserDocumentId(email) + val mutableDocument = MutableDocument(documentId, data) + try { + val database = databaseManager.inventoryDatabase + database?.save(mutableDocument) + } catch (e: CouchbaseLiteException) { + android.util.Log.e(e.message, e.stackTraceToString()) + return@withContext false + } + return@withContext true + } + } + + override suspend fun count(): Int { + return withContext(Dispatchers.IO) { + val database = databaseManager.inventoryDatabase + database?.let { db -> + val query = "SELECT COUNT(*) AS count FROM _ WHERE documentType='$userProfileType'" + val results = db.createQuery(query).execute().allResults() + return@withContext results[0].getInt("count") + } + return@withContext 0 + } + } + + override suspend fun delete(documentId: String): Boolean { + return withContext(Dispatchers.IO) { + var result = false + val database = databaseManager.inventoryDatabase + database?.let { db -> + val document = db.getDocument(documentId) + document?.let { + db.delete(it) + result = true + } + } + return@withContext result + } + } + + private fun getCurrentUserDocumentId(currentUser: String): String { + return "user::${currentUser}" + } +} \ No newline at end of file diff --git a/src/app/src/main/java/com/couchbase/learningpath/ui/developer/DevDatabaseInfoViewModel.kt b/src/app/src/main/java/com/couchbase/learningpath/ui/developer/DevDatabaseInfoViewModel.kt index d100278..ede44e7 100644 --- a/src/app/src/main/java/com/couchbase/learningpath/ui/developer/DevDatabaseInfoViewModel.kt +++ b/src/app/src/main/java/com/couchbase/learningpath/ui/developer/DevDatabaseInfoViewModel.kt @@ -9,17 +9,17 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -import com.couchbase.learningpath.data.KeyValueRepository import com.couchbase.learningpath.data.audits.AuditRepository import com.couchbase.learningpath.data.project.ProjectRepository import com.couchbase.learningpath.data.stockItem.StockItemRepository +import com.couchbase.learningpath.data.userprofile.UserProfileRepository import com.couchbase.learningpath.data.warehouse.WarehouseRepository import com.couchbase.learningpath.services.AuthenticationService import kotlinx.coroutines.ExperimentalCoroutinesApi @kotlinx.serialization.ExperimentalSerializationApi class DevDatabaseInfoViewModel( - private val userProfileRepository: KeyValueRepository, + private val userProfileRepository: UserProfileRepository, private val warehouseRepository: WarehouseRepository, private val projectRepository: ProjectRepository, private val auditRepository: AuditRepository, diff --git a/src/app/src/main/java/com/couchbase/learningpath/ui/profile/UserProfileViewModel.kt b/src/app/src/main/java/com/couchbase/learningpath/ui/profile/UserProfileViewModel.kt index 37f8be4..b952c49 100644 --- a/src/app/src/main/java/com/couchbase/learningpath/ui/profile/UserProfileViewModel.kt +++ b/src/app/src/main/java/com/couchbase/learningpath/ui/profile/UserProfileViewModel.kt @@ -16,14 +16,14 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import java.io.ByteArrayOutputStream -import com.couchbase.learningpath.data.KeyValueRepository import com.couchbase.learningpath.services.AuthenticationService import com.couchbase.learningpath.R +import com.couchbase.learningpath.data.userprofile.UserProfileRepository import com.couchbase.learningpath.models.User class UserProfileViewModel( application: Application, - private val repository: KeyValueRepository, + private val repository: UserProfileRepository, private val authService: AuthenticationService ) : AndroidViewModel(application) { From fb0f748e39a3a2bca0e77ab0803858e90c8692b8 Mon Sep 17 00:00:00 2001 From: Jeff Lockhart Date: Mon, 9 Jan 2023 22:31:33 -0700 Subject: [PATCH 5/9] Initialize databases before broadcasting user --- .../learningpath/DatabaseIntegrationTests.kt | 7 +++++-- .../services/AuthenticationService.kt | 2 +- .../services/MockAuthenticationService.kt | 17 ++++++++++++++--- .../learningpath/ui/login/LoginView.kt | 10 ++++++++-- .../learningpath/ui/login/LoginViewModel.kt | 9 ++------- 5 files changed, 30 insertions(+), 15 deletions(-) diff --git a/src/app/src/androidTest/java/com/couchbase/learningpath/DatabaseIntegrationTests.kt b/src/app/src/androidTest/java/com/couchbase/learningpath/DatabaseIntegrationTests.kt index 8ee419c..702f649 100644 --- a/src/app/src/androidTest/java/com/couchbase/learningpath/DatabaseIntegrationTests.kt +++ b/src/app/src/androidTest/java/com/couchbase/learningpath/DatabaseIntegrationTests.kt @@ -14,6 +14,7 @@ import com.couchbase.learningpath.models.* import com.couchbase.learningpath.services.MockAuthenticationService import com.couchbase.lite.CouchbaseLiteException import kotlinx.coroutines.flow.* +import kotlinx.coroutines.runBlocking import kotlinx.coroutines.test.runTest import org.junit.* import org.junit.Assert.* @@ -80,8 +81,10 @@ class DatabaseIntegrationTests { databaseManager.deleteDatabases() databaseManager.initializeDatabases(user1) - authenticationService = MockAuthenticationService() - val isAuth = authenticationService.authenticatedUser(user1.username, user1.password) + authenticationService = MockAuthenticationService(databaseManager) + val isAuth = runBlocking { + authenticationService.authenticatedUser(user1.username, user1.password) + } //arrange repositories auditRepository = AuditRepositoryDb(authenticationService, databaseManager) diff --git a/src/app/src/main/java/com/couchbase/learningpath/services/AuthenticationService.kt b/src/app/src/main/java/com/couchbase/learningpath/services/AuthenticationService.kt index 7a52be4..7de7ffb 100644 --- a/src/app/src/main/java/com/couchbase/learningpath/services/AuthenticationService.kt +++ b/src/app/src/main/java/com/couchbase/learningpath/services/AuthenticationService.kt @@ -6,6 +6,6 @@ import com.couchbase.learningpath.models.User interface AuthenticationService { val currentUser: LiveData fun getCurrentUser() : User - fun authenticatedUser(username: String, password: String) : Boolean + suspend fun authenticatedUser(username: String, password: String) : Boolean fun logout() } \ No newline at end of file diff --git a/src/app/src/main/java/com/couchbase/learningpath/services/MockAuthenticationService.kt b/src/app/src/main/java/com/couchbase/learningpath/services/MockAuthenticationService.kt index 4f41071..c202d1e 100644 --- a/src/app/src/main/java/com/couchbase/learningpath/services/MockAuthenticationService.kt +++ b/src/app/src/main/java/com/couchbase/learningpath/services/MockAuthenticationService.kt @@ -2,9 +2,14 @@ package com.couchbase.learningpath.services import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData +import com.couchbase.learningpath.data.DatabaseManager import com.couchbase.learningpath.models.User +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext -class MockAuthenticationService : AuthenticationService { +class MockAuthenticationService( + private val databaseManager: DatabaseManager +) : AuthenticationService { private var _user = MutableLiveData() private var _mockUsers = HashMap() @@ -15,11 +20,17 @@ class MockAuthenticationService : AuthenticationService { return _user.value ?: User("", "", "") } - override fun authenticatedUser(username: String, password: String): Boolean { + override suspend fun authenticatedUser(username: String, password: String): Boolean { return if (_mockUsers.containsKey(username)){ val user = _mockUsers[username] if (user?.password == password){ - _user.value = user + withContext(Dispatchers.IO) { + //initialize database if needed + databaseManager.initializeDatabases(user) + withContext(Dispatchers.Main) { + _user.value = user + } + } true } else { false diff --git a/src/app/src/main/java/com/couchbase/learningpath/ui/login/LoginView.kt b/src/app/src/main/java/com/couchbase/learningpath/ui/login/LoginView.kt index 5f96dfa..d712ede 100644 --- a/src/app/src/main/java/com/couchbase/learningpath/ui/login/LoginView.kt +++ b/src/app/src/main/java/com/couchbase/learningpath/ui/login/LoginView.kt @@ -8,6 +8,7 @@ import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.* import androidx.compose.runtime.Composable import androidx.compose.runtime.livedata.observeAsState +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color @@ -28,6 +29,7 @@ import com.google.accompanist.drawablepainter.rememberDrawablePainter import com.couchbase.learningpath.R import com.couchbase.learningpath.ui.theme.LearningPathTheme import com.couchbase.learningpath.ui.theme.Red500 +import kotlinx.coroutines.launch @Composable fun LoginView(onSuccessLogin: () -> Unit, @@ -36,12 +38,16 @@ fun LoginView(onSuccessLogin: () -> Unit, val password = viewModel.password.observeAsState("") val isError = viewModel.isError.observeAsState(false) + val composableScope = rememberCoroutineScope() + //**** //checks authentication if works, route to UserProfile //**** val onLoginCheck: () -> Unit = { - if (viewModel.login()) { - onSuccessLogin() + composableScope.launch { + if (viewModel.login()) { + onSuccessLogin() + } } } diff --git a/src/app/src/main/java/com/couchbase/learningpath/ui/login/LoginViewModel.kt b/src/app/src/main/java/com/couchbase/learningpath/ui/login/LoginViewModel.kt index bc9c685..5729ee5 100644 --- a/src/app/src/main/java/com/couchbase/learningpath/ui/login/LoginViewModel.kt +++ b/src/app/src/main/java/com/couchbase/learningpath/ui/login/LoginViewModel.kt @@ -34,17 +34,12 @@ class LoginViewModel( private val _isError = MutableLiveData(false) val isError: LiveData = _isError - fun login(): Boolean { + suspend fun login(): Boolean { _username.value?.let { uname -> _password.value?.let { pwd -> if (authenticationService.authenticatedUser(username = uname, password = pwd)) { _isError.value = false - val currentUser = authenticationService.getCurrentUser() - viewModelScope.launch(Dispatchers.IO) { - //initialize database if needed - databaseManager.initializeDatabases(currentUser) - replicatorService.updateAuthentication(isReset = false) - } + replicatorService.updateAuthentication(isReset = false) return true } } From fa4d6627a8f4e69b33515a83b0b7841d081a0593 Mon Sep 17 00:00:00 2001 From: Jeff Lockhart Date: Mon, 9 Jan 2023 22:32:36 -0700 Subject: [PATCH 6/9] Share UserProfileViewModel between drawer and UserProfileView Allow drawer to see user profile updates --- .../main/java/com/couchbase/learningpath/InventoryNavGraph.kt | 3 ++- .../main/java/com/couchbase/learningpath/ui/MainActivity.kt | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/app/src/main/java/com/couchbase/learningpath/InventoryNavGraph.kt b/src/app/src/main/java/com/couchbase/learningpath/InventoryNavGraph.kt index 3da381e..b865547 100644 --- a/src/app/src/main/java/com/couchbase/learningpath/InventoryNavGraph.kt +++ b/src/app/src/main/java/com/couchbase/learningpath/InventoryNavGraph.kt @@ -66,6 +66,7 @@ fun InventoryNavGraph( navController: NavHostController = rememberNavController(), scaffoldState: ScaffoldState = rememberScaffoldState(), scope: CoroutineScope = rememberCoroutineScope(), + userProfileViewModel: UserProfileViewModel, startDestination: String = MainDestinations.LOGIN_ROUTE) { val actions = remember(navController) { MainActions(navController) } NavHost(navController = navController, @@ -192,7 +193,7 @@ fun InventoryNavGraph( UserProfileView( openDrawer = openDrawer, scaffoldState = scaffoldState, - viewModel = getViewModel()) + viewModel = userProfileViewModel) } composable(MainDestinations.DEVELOPER_ROUTE){ diff --git a/src/app/src/main/java/com/couchbase/learningpath/ui/MainActivity.kt b/src/app/src/main/java/com/couchbase/learningpath/ui/MainActivity.kt index aae300d..9adced7 100644 --- a/src/app/src/main/java/com/couchbase/learningpath/ui/MainActivity.kt +++ b/src/app/src/main/java/com/couchbase/learningpath/ui/MainActivity.kt @@ -117,7 +117,8 @@ class MainActivity : ComponentActivity() { openDrawer = { openDrawer() }, navController = navController, scaffoldState = scaffoldState, - scope = scope + scope = scope, + profileViewModel ) } } From 89c6e30bdfe4f3b25bb26dd1ba160a3d04e71d65 Mon Sep 17 00:00:00 2001 From: Jeff Lockhart Date: Mon, 9 Jan 2023 23:03:38 -0700 Subject: [PATCH 7/9] Remove unused imports --- .../learningpath/data/userprofile/UserProfileRepositoryDb.kt | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/app/src/main/java/com/couchbase/learningpath/data/userprofile/UserProfileRepositoryDb.kt b/src/app/src/main/java/com/couchbase/learningpath/data/userprofile/UserProfileRepositoryDb.kt index 24db492..261564b 100644 --- a/src/app/src/main/java/com/couchbase/learningpath/data/userprofile/UserProfileRepositoryDb.kt +++ b/src/app/src/main/java/com/couchbase/learningpath/data/userprofile/UserProfileRepositoryDb.kt @@ -4,11 +4,7 @@ import com.couchbase.lite.CouchbaseLiteException import com.couchbase.lite.MutableDocument import com.couchbase.learningpath.data.DatabaseManager -import com.couchbase.lite.documentChangeFlow import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.emptyFlow -import kotlinx.coroutines.flow.map import kotlinx.coroutines.withContext class UserProfileRepositoryDb( From 251b79d707f845193292920458b6217f30d464b6 Mon Sep 17 00:00:00 2001 From: Jeff Lockhart Date: Mon, 9 Jan 2023 23:39:04 -0700 Subject: [PATCH 8/9] Only update profile pic Blob when pic changes Avoid recompressing the same pic repeatedly --- .../learningpath/data/KeyValueRepository.kt | 2 +- .../userprofile/UserProfileRepositoryDb.kt | 2 +- .../ui/profile/UserProfileViewModel.kt | 18 ++++++++++-------- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/app/src/main/java/com/couchbase/learningpath/data/KeyValueRepository.kt b/src/app/src/main/java/com/couchbase/learningpath/data/KeyValueRepository.kt index 5b71246..fecb0e6 100644 --- a/src/app/src/main/java/com/couchbase/learningpath/data/KeyValueRepository.kt +++ b/src/app/src/main/java/com/couchbase/learningpath/data/KeyValueRepository.kt @@ -4,5 +4,5 @@ interface KeyValueRepository { suspend fun count(): Int suspend fun get(key: String): Map - suspend fun save(data: Map) : Boolean + suspend fun save(data: Map) : Boolean } \ No newline at end of file diff --git a/src/app/src/main/java/com/couchbase/learningpath/data/userprofile/UserProfileRepositoryDb.kt b/src/app/src/main/java/com/couchbase/learningpath/data/userprofile/UserProfileRepositoryDb.kt index 261564b..e003a26 100644 --- a/src/app/src/main/java/com/couchbase/learningpath/data/userprofile/UserProfileRepositoryDb.kt +++ b/src/app/src/main/java/com/couchbase/learningpath/data/userprofile/UserProfileRepositoryDb.kt @@ -41,7 +41,7 @@ class UserProfileRepositoryDb( } } - override suspend fun save(data: Map): Boolean { + override suspend fun save(data: Map): Boolean { return withContext(Dispatchers.IO) { val email = data["email"] as String val documentId = getCurrentUserDocumentId(email) diff --git a/src/app/src/main/java/com/couchbase/learningpath/ui/profile/UserProfileViewModel.kt b/src/app/src/main/java/com/couchbase/learningpath/ui/profile/UserProfileViewModel.kt index b952c49..1f72500 100644 --- a/src/app/src/main/java/com/couchbase/learningpath/ui/profile/UserProfileViewModel.kt +++ b/src/app/src/main/java/com/couchbase/learningpath/ui/profile/UserProfileViewModel.kt @@ -49,6 +49,8 @@ class UserProfileViewModel( var profilePic by mutableStateOf(defaultProfilePic()) private set + private var profilePicBlob: Blob? = null + private fun defaultProfilePic(): Bitmap { return BitmapFactory.decodeResource(getApplication().resources, R.drawable.profile_placeholder) } @@ -66,7 +68,8 @@ class UserProfileViewModel( givenName = userProfile["givenName"] as? String ?: "" surname = userProfile["surname"] as? String ?: "" jobTitle = userProfile["jobTitle"] as? String ?: "" - profilePic = (userProfile["imageData"] as? Blob)?.let { blob -> + profilePicBlob = userProfile["imageData"] as? Blob + profilePic = profilePicBlob?.let { blob -> val d = Drawable.createFromStream(blob.contentStream, "res") d?.toBitmap() } ?: defaultProfilePic() @@ -96,9 +99,10 @@ class UserProfileViewModel( } val onProfilePicChanged: (Bitmap) -> Unit = { newValue -> - viewModelScope.launch(Dispatchers.Main) { - profilePic = newValue - } + profilePic = newValue + val outputStream = ByteArrayOutputStream() + newValue.compress(Bitmap.CompressFormat.JPEG, 100, outputStream) + profilePicBlob = Blob("image/jpeg", outputStream.toByteArray()) } val clearToastMessage: () -> Unit = { @@ -109,16 +113,14 @@ class UserProfileViewModel( //when saving information to the database need to make sure //to use Dispatchers.IO so that Disk I/O work isn't done on the main thread viewModelScope.launch(Dispatchers.IO) { - val profile = HashMap() + val profile = HashMap() profile["givenName"] = givenName profile["surname"] = surname profile["jobTitle"] = jobTitle profile["email"] = emailAddress profile["team"] = team profile["documentType"] = "user" - val outputStream = ByteArrayOutputStream() - profilePic.compress(Bitmap.CompressFormat.JPEG, 100, outputStream) - profile["imageData"] = Blob("image/jpeg", outputStream.toByteArray()) + profile["imageData"] = profilePicBlob val didSave = repository.save(profile) //make sure when we update the UI we update on the Main Thread From 9e793af060c5ad020272dd932afdfae293e0570b Mon Sep 17 00:00:00 2001 From: Jeff Lockhart Date: Tue, 10 Jan 2023 11:23:29 -0700 Subject: [PATCH 9/9] Change unnecessary vars to vals --- .../learningpath/services/MockAuthenticationService.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/src/main/java/com/couchbase/learningpath/services/MockAuthenticationService.kt b/src/app/src/main/java/com/couchbase/learningpath/services/MockAuthenticationService.kt index c202d1e..3acfaa5 100644 --- a/src/app/src/main/java/com/couchbase/learningpath/services/MockAuthenticationService.kt +++ b/src/app/src/main/java/com/couchbase/learningpath/services/MockAuthenticationService.kt @@ -11,8 +11,8 @@ class MockAuthenticationService( private val databaseManager: DatabaseManager ) : AuthenticationService { - private var _user = MutableLiveData() - private var _mockUsers = HashMap() + private val _user = MutableLiveData() + private val _mockUsers = HashMap() override val currentUser: LiveData = _user