Skip to content

Commit 978b23e

Browse files
committed
Observe authenticated user to update profile
1 parent 04b8f26 commit 978b23e

File tree

5 files changed

+93
-63
lines changed

5 files changed

+93
-63
lines changed

src/app/src/main/java/com/couchbase/learningpath/services/AuthenticationService.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
package com.couchbase.learningpath.services
22

3+
import androidx.lifecycle.LiveData
34
import com.couchbase.learningpath.models.User
45

56
interface AuthenticationService {
7+
val currentUser: LiveData<User?>
68
fun getCurrentUser() : User
79
fun authenticatedUser(username: String, password: String) : Boolean
810
fun logout()

src/app/src/main/java/com/couchbase/learningpath/services/MockAuthenticationService.kt

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,37 @@
11
package com.couchbase.learningpath.services
22

3+
import androidx.lifecycle.LiveData
4+
import androidx.lifecycle.MutableLiveData
35
import com.couchbase.learningpath.models.User
46

57
class MockAuthenticationService : AuthenticationService {
68

7-
private var _user: User? = null
9+
private var _user = MutableLiveData<User?>()
810
private var _mockUsers = HashMap<String, User>()
911

12+
override val currentUser: LiveData<User?> = _user
13+
1014
override fun getCurrentUser(): User {
11-
return _user?: User("", "", "")
15+
return _user.value ?: User("", "", "")
1216
}
1317

1418
override fun authenticatedUser(username: String, password: String): Boolean {
1519
return if (_mockUsers.containsKey(username)){
1620
val user = _mockUsers[username]
1721
if (user?.password == password){
18-
_user = user
22+
_user.value = user
1923
true
2024
} else {
2125
false
2226
}
2327
} else {
24-
_user = User(username = username, password = password, team = "team2")
28+
_user.value = User(username = username, password = password, team = "team2")
2529
return true
2630
}
2731
}
2832

2933
override fun logout() {
30-
_user = null
34+
_user.value = null
3135
}
3236

3337
init {

src/app/src/main/java/com/couchbase/learningpath/ui/MainActivity.kt

Lines changed: 19 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import androidx.activity.ComponentActivity
66
import androidx.activity.compose.setContent
77
import androidx.compose.material.*
88
import androidx.compose.runtime.*
9+
import androidx.compose.runtime.livedata.observeAsState
910
import androidx.compose.ui.Modifier
1011
import androidx.compose.ui.platform.LocalLifecycleOwner
1112
import androidx.compose.ui.res.stringResource
@@ -24,18 +25,22 @@ import org.koin.androidx.compose.getViewModel
2425
import com.couchbase.learningpath.InventoryNavGraph
2526
import com.couchbase.learningpath.MainDestinations
2627
import com.couchbase.learningpath.R
27-
import com.couchbase.learningpath.data.KeyValueRepository
2828
import com.couchbase.learningpath.services.AuthenticationService
2929
import com.couchbase.learningpath.services.ReplicatorService
3030
import com.couchbase.learningpath.ui.components.Drawer
3131
import com.couchbase.learningpath.ui.profile.UserProfileViewModel
3232
import com.couchbase.learningpath.ui.theme.LearningPathTheme
3333
import kotlinx.coroutines.ExperimentalCoroutinesApi
34+
import org.koin.androidx.viewmodel.ext.android.viewModel
3435

3536
@kotlinx.serialization.ExperimentalSerializationApi
3637
@ExperimentalMaterialApi
3738
@ExperimentalCoroutinesApi
3839
class MainActivity : ComponentActivity() {
40+
41+
//used for drawing profile in drawer
42+
private val profileViewModel: UserProfileViewModel by viewModel()
43+
3944
override fun onCreate(savedInstanceState: Bundle?) {
4045
super.onCreate(savedInstanceState)
4146

@@ -45,18 +50,13 @@ class MainActivity : ComponentActivity() {
4550
val navController = rememberNavController()
4651
val scaffoldState = rememberScaffoldState()
4752
val authService: AuthenticationService by inject()
48-
val userProfileRepository: KeyValueRepository by inject()
4953
val replicatorService : ReplicatorService by inject()
5054
val menuResource = stringResource(id = R.string.btnMenu)
5155
val mainViewModel = getViewModel<MainViewModel>()
5256

53-
//used for drawing profile in drawer
54-
var profileViewModel: UserProfileViewModel? = null
55-
5657
fun logout() {
5758
replicatorService.stopReplication()
5859
replicatorService.updateAuthentication(isReset = true)
59-
profileViewModel = null
6060
authService.logout()
6161
}
6262
//we need a drawer overflow menu on multiple screens
@@ -65,14 +65,6 @@ class MainActivity : ComponentActivity() {
6565
val drawerState = rememberDrawerState(DrawerValue.Closed)
6666
val openDrawer = {
6767
scope.launch {
68-
if (profileViewModel == null) {
69-
profileViewModel = UserProfileViewModel(
70-
repository = userProfileRepository,
71-
authService = authService,
72-
mainViewModel.context)
73-
} else {
74-
profileViewModel?.updateUserProfileInfo()
75-
}
7668
drawerState.open()
7769
}
7870
}
@@ -83,17 +75,24 @@ class MainActivity : ComponentActivity() {
8375
scaffoldState.snackbarHostState
8476
}) {
8577
ModalDrawer(
86-
modifier = Modifier.semantics { contentDescription = menuResource },
78+
modifier = Modifier
79+
.semantics { contentDescription = menuResource },
8780
drawerState = drawerState,
8881
gesturesEnabled = drawerState.isOpen,
8982
drawerContent = {
83+
val givenName by profileViewModel.givenName.observeAsState()
84+
val surname by profileViewModel.surname.observeAsState()
85+
val emailAddress by profileViewModel.emailAddress.observeAsState()
86+
val team by profileViewModel.team.observeAsState()
87+
val profilePic by profileViewModel.profilePic.observeAsState()
88+
9089
Drawer(
9190
modifier = Modifier.semantics { contentDescription = "{$menuResource}1" },
92-
firstName = profileViewModel?.givenName?.value,
93-
lastName = profileViewModel?.surname?.value,
94-
email = profileViewModel?.emailAddress?.value,
95-
team = profileViewModel?.team?.value,
96-
profilePicture = profileViewModel?.profilePic?.value,
91+
firstName = givenName,
92+
lastName = surname,
93+
email = emailAddress,
94+
team = team,
95+
profilePicture = profilePic,
9796
onClicked = { route ->
9897
scope.launch {
9998
drawerState.close()

src/app/src/main/java/com/couchbase/learningpath/ui/profile/UserProfileView.kt

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import androidx.compose.material.*
1818
import androidx.compose.material.icons.Icons
1919
import androidx.compose.material.icons.filled.Menu
2020
import androidx.compose.runtime.*
21+
import androidx.compose.runtime.livedata.observeAsState
2122
import androidx.compose.ui.Alignment
2223
import androidx.compose.ui.Modifier
2324
import androidx.compose.ui.draw.clip
@@ -42,7 +43,15 @@ import com.couchbase.learningpath.ui.theme.Red500
4243
fun UserProfileView(
4344
openDrawer: () -> Unit,
4445
scaffoldState: ScaffoldState = rememberScaffoldState(),
45-
viewModel: UserProfileViewModel) {
46+
viewModel: UserProfileViewModel
47+
) {
48+
val givenName by viewModel.givenName.observeAsState("")
49+
val surname by viewModel.surname.observeAsState("")
50+
val jobTitle by viewModel.jobTitle.observeAsState("")
51+
val profilePic by viewModel.profilePic.observeAsState()
52+
val emailAddress by viewModel.emailAddress.observeAsState("")
53+
val team by viewModel.team.observeAsState("")
54+
val toastMessage by viewModel.toastMessage.observeAsState()
4655

4756
LearningPathTheme {
4857
// A surface container using the 'background' color from the theme
@@ -59,17 +68,17 @@ fun UserProfileView(
5968
)
6069
{
6170
UserProfileFormWidget(
62-
viewModel.givenName.value,
71+
givenName,
6372
viewModel.onGivenNameChanged,
64-
viewModel.surname.value,
73+
surname,
6574
viewModel.onSurnameChanged,
66-
viewModel.jobTitle.value,
75+
jobTitle,
6776
viewModel.onJobTitleChanged,
68-
viewModel.profilePic.value,
77+
profilePic,
6978
viewModel.onProfilePicChanged,
70-
viewModel.emailAddress.value,
71-
viewModel.team.value,
72-
viewModel.toastMessage.value,
79+
emailAddress,
80+
team,
81+
toastMessage,
7382
viewModel.onSave,
7483
viewModel.clearToastMessage
7584
)

src/app/src/main/java/com/couchbase/learningpath/ui/profile/UserProfileViewModel.kt

Lines changed: 46 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@ import android.content.Context
44
import android.graphics.Bitmap
55
import android.graphics.BitmapFactory
66
import android.graphics.drawable.Drawable
7-
import androidx.compose.runtime.mutableStateOf
87
import androidx.core.graphics.drawable.toBitmap
8+
import androidx.lifecycle.LiveData
9+
import androidx.lifecycle.MutableLiveData
10+
import androidx.lifecycle.Observer
911
import androidx.lifecycle.ViewModel
1012
import androidx.lifecycle.viewModelScope
1113
import com.couchbase.lite.Blob
@@ -22,76 +24,90 @@ import com.couchbase.learningpath.models.User
2224

2325
class UserProfileViewModel(
2426
private val repository: KeyValueRepository,
25-
authService: AuthenticationService,
27+
private val authService: AuthenticationService,
2628
context: WeakReference<Context>
2729
) : ViewModel() {
2830

29-
private var currentUser: User? = null
30-
3131
//track our fields in our composable
32-
var givenName = mutableStateOf("")
33-
var surname = mutableStateOf("")
34-
var jobTitle = mutableStateOf("")
35-
var emailAddress = mutableStateOf("")
36-
var team = mutableStateOf("")
37-
var toastMessage = mutableStateOf("")
38-
var profilePic = mutableStateOf<Bitmap?>(null)
32+
private val _givenName = MutableLiveData("")
33+
val givenName: LiveData<String> = _givenName
3934

40-
init {
41-
currentUser = authService.getCurrentUser()
42-
updateUserProfileInfo()
43-
profilePic.value = BitmapFactory.decodeResource(context.get()?.resources, R.drawable.profile_placeholder)
44-
}
35+
private val _surname = MutableLiveData("")
36+
val surname: LiveData<String> = _surname
37+
38+
private val _jobTitle = MutableLiveData("")
39+
val jobTitle: LiveData<String> = _jobTitle
40+
41+
private val _emailAddress = MutableLiveData("")
42+
val emailAddress: LiveData<String> = _emailAddress
43+
44+
private val _team = MutableLiveData("")
45+
val team: LiveData<String> = _team
46+
47+
private val _toastMessage = MutableLiveData("")
48+
val toastMessage: LiveData<String> = _toastMessage
4549

46-
fun updateUserProfileInfo() {
50+
private val _profilePic = MutableLiveData<Bitmap?>(null)
51+
val profilePic: LiveData<Bitmap?> = _profilePic
52+
53+
private val userObserver: (User?) -> Unit = { currentUser ->
4754
currentUser?.let { authenticatedUser ->
48-
emailAddress.value = authenticatedUser.username
49-
team.value = authenticatedUser.team
55+
_emailAddress.value = authenticatedUser.username
56+
_team.value = authenticatedUser.team
5057
//when getting information from the database need to make sure
5158
//to use Dispatchers.IO so that Disk I/O work isn't done on the main thread
5259
viewModelScope.launch(Dispatchers.IO) {
5360
val userProfile = repository.get(authenticatedUser.username)
5461
//make sure when we update the UI we update on the Main Thread
5562
withContext(Dispatchers.Main) {
5663
userProfile["givenName"]?.let {
57-
givenName.value = userProfile["givenName"] as String
64+
_givenName.value = userProfile["givenName"] as String
5865
}
5966
userProfile["surname"]?.let {
60-
surname.value = userProfile["surname"] as String
67+
_surname.value = userProfile["surname"] as String
6168
}
6269
userProfile["jobTitle"]?.let {
63-
jobTitle.value = userProfile["jobTitle"] as String
70+
_jobTitle.value = userProfile["jobTitle"] as String
6471
}
6572
userProfile["imageData"]?.let {
6673
val blob = userProfile["imageData"] as Blob
6774
val d = Drawable.createFromStream(blob.contentStream, "res")
68-
profilePic.value = d?.toBitmap()
75+
_profilePic.value = d?.toBitmap()
6976
}
7077
}
7178
}
7279
}
7380
}
7481

82+
init {
83+
authService.currentUser.observeForever(userObserver)
84+
_profilePic.value = BitmapFactory.decodeResource(context.get()?.resources, R.drawable.profile_placeholder)
85+
}
86+
87+
override fun onCleared() {
88+
authService.currentUser.removeObserver(userObserver)
89+
}
90+
7591
val onGivenNameChanged: (String) -> Unit = { newValue ->
76-
givenName.value = newValue
92+
_givenName.value = newValue
7793
}
7894

7995
val onSurnameChanged: (String) -> Unit = { newValue ->
80-
surname.value = newValue
96+
_surname.value = newValue
8197
}
8298

8399
val onJobTitleChanged: (String) -> Unit = { newValue ->
84-
jobTitle.value = newValue
100+
_jobTitle.value = newValue
85101
}
86102

87103
val onProfilePicChanged: (Bitmap) -> Unit = { newValue ->
88104
viewModelScope.launch(Dispatchers.Main) {
89-
profilePic.value = newValue
105+
_profilePic.value = newValue
90106
}
91107
}
92108

93109
val clearToastMessage: () -> Unit = {
94-
toastMessage.value = ""
110+
_toastMessage.value = ""
95111
}
96112

97113
val onSave: () -> Unit = {
@@ -116,9 +132,9 @@ class UserProfileViewModel(
116132
//make sure when we update the UI we update on the Main Thread
117133
withContext(Dispatchers.Main) {
118134
if (didSave) {
119-
toastMessage.value = "Successfully updated profile"
135+
_toastMessage.value = "Successfully updated profile"
120136
} else {
121-
toastMessage.value = "Error saving, try again later."
137+
_toastMessage.value = "Error saving, try again later."
122138
}
123139
}
124140
}

0 commit comments

Comments
 (0)