How to convert base64 string into image (Android, Kotlin, Jetpack Compose) - android

Implementing profile modifications. This feature allows the server to request a value for image change and nickname change using Multipart for retrofit in a form-data manner, which passes the modified nickname value and the encoded image value as the response value. I'm going to decode the encoded image response value and put it in as the factor value of the Image Composable.
I need to transform that String response value into a BitMap image again to use it on a Image Composable in my Android app
How to do it?
API:
fun changeNicknameAndProfile(
token: String,
nickname: String,
body: MultipartBody.Part,
routeAction: RouteAction,
response: (ChangeNicknameAndProfileResponse?) -> Unit
) {
var changeNicknameAndProfileResponse: ChangeNicknameAndProfileResponse? = null
var retrofit = Retrofit.Builder().baseUrl("https://plotustodo-ctzhc.run.goorm.io/")
.addConverterFactory(GsonConverterFactory.create()).build()
var changeNicknameAndProfileRequest: ChangeNicknameAndProfileRequest =
retrofit.create(ChangeNicknameAndProfileRequest::class.java)
changeNicknameAndProfileRequest.requestChangeNicknameAndProfile(token, nickname, body)
.enqueue(object : Callback<ChangeNicknameAndProfileResponse> {
// 성공 했을때
override fun onResponse(
call: Call<ChangeNicknameAndProfileResponse>,
response: Response<ChangeNicknameAndProfileResponse>,
) {
changeNicknameAndProfileResponse = response.body()
when(changeNicknameAndProfileResponse?.resultCode) {
"200" -> {
MyApplication.prefs.setData("nickname", nickname)
MyApplication.prefs.setData("image", changeNicknameAndProfileResponse!!.data.image)
response(changeNicknameAndProfileResponse)
routeAction.goBack()
Log.d("changeNickname&Profile",
"resultCode : " + changeNicknameAndProfileResponse?.resultCode)
Log.d("changeNickname&Profile",
"resultCode : " + changeNicknameAndProfileResponse?.data)
}
"500" -> {
Log.d("changeNickname&Profile",
"resultCode : " + changeNicknameAndProfileResponse?.resultCode)
}
}
}
// 실패 했을때
override fun onFailure(call: Call<ChangeNicknameAndProfileResponse>, t: Throwable) {
Log.e("changeNickname&Profile", t.message.toString())
}
})
}
To need values:
var nickname by remember { mutableStateOf(MyApplication.prefs.getData("nickname", "")) }
val token = "Token ${MyApplication.prefs.getData("token", "")}"
var openDialog by remember { mutableStateOf(false) }
val imageUri = rememberSaveable {
mutableStateOf<Uri?>(null)
}
val painter = rememberImagePainter(
data = imageUri.value,
builder = {
if (imageUri.value != null) {
placeholder(R.drawable.defaultprofile)
}
}
)
val launcher =
rememberLauncherForActivityResult(contract = ActivityResultContracts.GetContent()) { uri: Uri? ->
uri?.let {
imageUri.value = it
Log.v("image", "image: ${uri}")
}
}
val file = imageUri.value?.let { uri ->
val contentResolver = LocalContext.current.contentResolver
val inputStream = contentResolver.openInputStream(uri)
val tempFile = File.createTempFile("image", null, LocalContext.current.cacheDir)
tempFile.outputStream().use { outputStream ->
inputStream?.copyTo(outputStream)
}
tempFile
}
val requestFile = file?.asRequestBody("image/jpeg".toMediaTypeOrNull())
val body = requestFile?.let {
MultipartBody.Part.createFormData("image", file.name, requestFile)
}
Composable:
TopAppBar(
title = {
Text(text = "프로필 수정")
},
navigationIcon = {
IconButton(onClick = {
routeAction.goBack()
}) {
Icon(imageVector = Icons.Filled.ArrowBack, contentDescription = "back")
}
},
actions = {
Text(
text = "완료",
modifier = Modifier
.padding(30.dp)
.clickable {
val currenNickname = MyApplication.prefs.getData("nickname", nickname)
if ((body != null) || !(nickname.equals(currenNickname))) {
changeNicknameAndProfile(token,
nickname,
body!!,
routeAction
) {
for (i in it!!.data.image) {
val base64String = i.toString()
val decodedBytes =
Base64.decode(base64String, Base64.DEFAULT)
val decodedImage = BitmapFactory.decodeByteArray(decodedBytes, 0, decodedBytes.size)
}
}
}
})
})
Spacer(modifier = Modifier.height(50.dp))
Image(
painter = painter,
contentDescription = "profileImage",
modifier = Modifier
.size(150.dp)
.padding(8.dp)
.clickable {
openDialog = !openDialog
},
contentScale = ContentScale.Crop
)

Related

how to Update my UI using android jetpack compose and retrofit with a new request data from retrofit

I have created an app using kotlin and android jetpack compose and dependency injection for creating my
retrofit and room datebase the problem is that I'm getting some data using retrofit from internet and showing them in my ui but when I want to send a request for the second time I get the information but
I don't know how to put them insted of existing list that I have .
It's like a movie app that I want to go to next page or load more data when the list ends
interface AppGameApi {
#GET("farsroid")
suspend fun getAppGameFromPage(
#Query("token") token: String = Constants.TOKEN,
#Query("action") action: String,
#Query("page") page: String
): AppGame
#GET("farsroid")
suspend fun getAppGameFromSearch(
#Query("token") token: String = Constants.TOKEN,
#Query("action") action: String = "search",
#Query("q") q: String = ""
): AppGame
#GET("farsroid")
suspend fun getAppGameFromDownload(
#Query("token") token: String = Constants.TOKEN,
#Query("action") action: String = "download",
#Query("link") link: String = ""
): AppGameDownload
}
class AppGameRetrofitRepository #Inject constructor(private val api: AppGameApi) {
private val appGames = DataOrException<AppGame,Boolean,Exception>()
private val appGamesSearch = DataOrException<AppGame,Boolean,Exception>()
private val appGamesDownload = DataOrException<AppGameDownload,Boolean,Exception>()
suspend fun getAllAppGames(page:String,action:String):DataOrException<AppGame,Boolean,java.lang.Exception>{
try {
appGames.isLoading = true
appGames.data = api.getAppGameFromPage(page = page, action = action)
if (appGames.data!!.status == 200) appGames.isLoading = false
Log.d("Mr", "getAllAppGames: ${appGames.data!!.result[0]}")
}catch (exception:Exception){
appGames.e = exception
}
return appGames
}
suspend fun getAllAppGamesSearch(q:String):DataOrException<AppGame,Boolean,java.lang.Exception>{
try {
appGamesSearch.isLoading = true
appGamesSearch.data = api.getAppGameFromSearch(q = q)
if (appGamesSearch.data!!.status == 200) appGamesSearch.isLoading = false
}catch (exception:Exception){
appGamesSearch.e = exception
Log.d("Error", "getAllAppGames: ${appGamesSearch.e!!.localizedMessage} ")
}
return appGamesSearch
}
suspend fun getAllAppGamesDownload(link:String):DataOrException<AppGameDownload,Boolean,java.lang.Exception>{
try {
appGamesDownload.isLoading = true
appGamesDownload.data = api.getAppGameFromDownload(link = link)
if (appGamesDownload.data!!.status == 200) appGamesDownload.isLoading = false
}catch (exception:Exception){
appGamesDownload.e = exception
Log.d("Error", "getAllAppGames: ${appGamesDownload.e!!.localizedMessage} ")
}
return appGamesDownload
}
}
#HiltViewModel
class AppGameRetrofitViewModel #Inject constructor(private val repository: AppGameRetrofitRepository) :
ViewModel() {
var appGame: MutableState<DataOrException<AppGame, Boolean, Exception>> =
mutableStateOf(DataOrException(null, true, Exception("")))
private val appGameSearch: MutableState<DataOrException<AppGame, Boolean, Exception>> =
mutableStateOf(DataOrException(null, true, Exception("")))
private val appGameDownload: MutableState<DataOrException<AppGameDownload, Boolean, Exception>> =
mutableStateOf(DataOrException(null, true, Exception("")))
init {
getAllAppGames("1","app")
}
fun getAllAppGames(
page: String,
action: String
): DataOrException<AppGame, Boolean, Exception> {
viewModelScope.launch {
appGame.value.isLoading = true
appGame.value = repository.getAllAppGames(page = page, action = action)
if (appGame.value.data!!.status == 200) appGame.value.isLoading = false
}
return appGame.value
}
private fun getAllAppGamesSearch(q: String = "") {
viewModelScope.launch {
appGameSearch.value.isLoading = true
appGameSearch.value = repository.getAllAppGamesSearch(q = q)
if (appGameSearch.value.data!!.status == 200) appGameSearch.value.isLoading = false
}
}
private fun getAllAppGamesDownload(link: String = "") {
viewModelScope.launch {
appGameDownload.value.isLoading = true
appGameDownload.value = repository.getAllAppGamesDownload(link = link)
if (appGameDownload.value.data!!.status == 200) appGameDownload.value.isLoading = false
}
}
}
I have tried these and my mainActivity looks like this
#AndroidEntryPoint
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
Android_Complete_TemplateTheme {
val appGameViewModelRoomDatabase by viewModels<RoomDatabaseViewModel>()
val appGameViewModelRetrofit by viewModels<AppGameRetrofitViewModel>()
val navController = rememberNavController()
val pageCounter = remember {
mutableStateOf("1")
}
val pageAction = remember {
mutableStateOf("game")
}
FarsroidNavigationSystem(
roomDatabaseViewModel = appGameViewModelRoomDatabase,
appGameRetrofitViewModel = appGameViewModelRetrofit,
navController = navController,
) {
if (appGameViewModelRetrofit.appGame.value.isLoading == true) {
AppGame(
status = 200,
result = listOf()
)
} else {
appGameViewModelRetrofit.getAllAppGames(
pageCounter.value,
pageAction.value
).data!!
}
}
pageCounter.value= pageCounter.value+1
}
}
}
}
#Composable
fun FarsroidNavigationSystem(
roomDatabaseViewModel: RoomDatabaseViewModel,
appGameRetrofitViewModel: AppGameRetrofitViewModel,
navController: NavHostController,
onClicked: () -> AppGame
) {
NavHost(
navController = navController,
startDestination = Screens.HomeScreen.name
) {
composable(route = Screens.HomeScreen.name) {
HomeScreen(
retrofitViewModel = appGameRetrofitViewModel,
navController = navController
){
onClicked()
}
}
composable(route = Screens.DetailScreen.name) {
DetailScreen(
roomDatabaseViewModel = roomDatabaseViewModel,
navController = navController
)
}
}
}
#Composable
fun HomeScreen(
retrofitViewModel: AppGameRetrofitViewModel,
navController: NavHostController,
onClicked: () -> AppGame
) {
Scaffold(
modifier = Modifier
.fillMaxSize(),
backgroundColor = Color.White,
contentColor = Color.DarkGray
) { paddingValues ->
Spacer(modifier = Modifier.padding(paddingValues))
if (onClicked().status != 200)
CircularProgressIndicator(modifier = Modifier.fillMaxSize(), color = Color.Red)
else
LazyColumn {
items(onClicked().result) {
ItemCard(resultAppGame = it)
}
}
Button(onClick = {
onClicked()
}) {
Text("LoadMore")
}
}
}
#Composable
fun ItemCard(
resultAppGame: ResultAppGame,
) {
Card(
modifier = Modifier
.fillMaxWidth()
.padding(10.dp),
shape = CutCornerShape(10.dp),
backgroundColor = Color.White,
contentColor = Color.DarkGray,
elevation = 10.dp
) {
Column(
modifier = Modifier.padding(10.dp),
verticalArrangement = Arrangement.Top,
horizontalAlignment = Alignment.CenterHorizontally
) {
Card(
modifier = Modifier
.size(150.dp)
.padding(20.dp),
elevation = 10.dp,
shape = CircleShape,
backgroundColor = Color.White,
border = BorderStroke(width = 0.5.dp, Color.LightGray)
) {
Image(
painter = rememberAsyncImagePainter(
model = resultAppGame.pic),
contentDescription = resultAppGame.pic,
contentScale = ContentScale.Fit
)
}
Divider(thickness = 1.dp, color = Color.Gray)
Text(text = resultAppGame.title, textAlign = TextAlign.Right)
Divider(thickness = 1.dp, color = Color.Gray)
Text(text = resultAppGame.description, textAlign = TextAlign.Right)
Divider(thickness = 1.dp, color = Color.Gray)
Text(text = resultAppGame.link)
}
}
}

How to save data to DataStore from retrofit response in android compose?

I'm trying to save access token data to jetpack DataStore from http response with Retrofit2.
But function of DataStore that save data is asynchronous task so this function execute before token data arrive and save empty data.
this is my UI Code:
#Composable
fun LoginBtn(
navigateToHome: () -> Unit,
authenticate: () -> Unit,
loginScreenViewModel: LoginScreenViewModel
){
val context = LocalContext.current
val scope = rememberCoroutineScope()
val tokenStore = TokenStore(context)
Button(
onClick = {
scope.launch {
val token = loginScreenViewModel.requestLogin()
tokenStore.saveAccessToken(token.access_token)
tokenStore.saveRefreshToken(token.refresh_token)
}
},
colors = ButtonDefaults.buttonColors(
containerColor = Carrot
),
shape = MaterialTheme.shapes.extraSmall,
modifier = Modifier
.fillMaxWidth()
.height(50.dp),
enabled = (loginScreenViewModel.email.value != "" && loginScreenViewModel.password.value != "")
) {
Text(text = "login", fontWeight = FontWeight.Bold, fontSize = 16.sp)
}
}
this is loginViewModel that call http request:
class LoginScreenViewModel(): ViewModel() {
private val _email = mutableStateOf("")
val email = _email
private val _password = mutableStateOf("")
val password = _password
fun setEmail(text: String) {
_email.value = text
}
fun setPassword(text: String) {
_password.value = text
}
fun requestLogin(): Token {
var token = Token("", "")
apiService.login(username = email.value, password = password.value)
.enqueue(object : Callback<LogInResponse> {
override fun onResponse(
call: Call<LogInResponse>,
response: Response<LogInResponse>
) {
Log.i("LOGIN RESPONSE", "access_token : ${response.body()?.access_token}")
token = Token(
access_token = response.body()?.access_token!!,
refresh_token = response.body()?.refresh_token!!
)
}
override fun onFailure(call: Call<LogInResponse>, t: Throwable) {
t.printStackTrace()
}
})
return token
}
}
this is DataStore code:
class TokenStore(
private val context: Context,
) {
companion object {
private val Context.datastore: DataStore<Preferences> by preferencesDataStore("token")
val ACCESS_TOKEN_KEY = stringPreferencesKey("access_token")
val REFRESH_TOKEN_KEY = stringPreferencesKey("refresh_token")
}
val getAccessToken: Flow<String> = context.datastore.data
.map { preference ->
preference[ACCESS_TOKEN_KEY] ?: ""
}
suspend fun saveAccessToken(access_token: String){
context.datastore.edit { preference ->
preference[ACCESS_TOKEN_KEY] = access_token
}
}
val getRefreshToken: Flow<String?> = context.datastore.data
.map { preference ->
preference[REFRESH_TOKEN_KEY] ?: ""
}
suspend fun saveRefreshToken(refresh_token: String){
context.datastore.edit { preference ->
preference[REFRESH_TOKEN_KEY] = refresh_token
}
}
}

Jetpack Compose Lazy Column single selection

I'm getting data from the server and displaying it in the list, each item can be selected with one click to display the button, but I cannot close it, I can only open it.
This is item of list class
data class Task(
val deviceName: String,
val deviceId: String,
var selected :Boolean= Boolean,
)
this is data class
data class TaskStatus(
val taskList: SnapshotStateList<Task> = SnapshotStateList(),
val selectedNumber: Int = -1,
)
My ViewModel
private val _status = MutableStateFlow(TaskStatus())
val status = _status.asStateFlow()
fun getList(){
...
for(item in result){
_status.value.taskList.add(task)
}
}
fun selectTask(task: Task) {
val list = _status.value.taskList
val selectNumber = _status.value.selectedNumber
val newSelectNumber = list.indexOf(task)
if (newSelectNumber != selectNumber) {
if (selectNumber != -1) {
list[selectNumber].selected.value = false
}
}
task.selected.value = !task.selected.value
_status.update { it.copy(selectedNumber = newSelectNumber) }
}
My LazyColumn
...
LazyColumn(
modifier = Modifier
.fillMaxWidth()
.weight(1F),
verticalArrangement = Arrangement.spacedBy(11.dp), contentPadding = PaddingValues(16.dp)
) {
items(
taskStatus.taskList,
key = { it.deviceId }) { task ->
Item(task)
}
}
#Compose
fun Item(task:Task){
Column(){
Text(text = task.name)
Text(text = task.deviceId)
if(task.selected){
Botton()
}
}
}
I can only show but not hide the button
Thank you in advance.
I can't compile your code directly so I tried to make my own implementation. I added a callback which will be triggered from your Task Item
Your TaskList composable
#Composable
fun TaskList(
taskList: SnapshotStateList<Task>,
onSelected: (Task) -> Unit
) {
LazyColumn(
modifier = Modifier
.fillMaxWidth(),
verticalArrangement = Arrangement.spacedBy(11.dp), contentPadding = PaddingValues(16.dp)
) {
items(
taskList,
key = { it.deviceId }) { task ->
Item(task) {
onSelected(it)
}
}
}
}
Your TaskItem Composable
#Composable
fun Item(
task:Task,
onSelected: (Task) -> Unit
){
Column(
modifier = Modifier.clickable {
onSelected(task) // selection callback
}
){
Text(text = task.deviceName)
Text(text = task.deviceId)
if(task.selected) {
Box(modifier = Modifier
.fillMaxWidth()
.height(100.dp)
.background(Color.Red)) {
}
}
}
}
And I mocked a VieModel
class TaskStateHolder {
private val _status = MutableStateFlow( TaskStatus (
taskList = mutableStateListOf(
Task(
deviceName = "Device 1",
deviceId = "Device 1 ID"
),
Task(
deviceName = "Device 2",
deviceId = "Device 2 ID"
),
Task(
deviceName = "Device 3",
deviceId = "Device 3 ID"
),
Task(
deviceName = "Device 4",
deviceId = "Device 4 ID"
),
)
))
val status = _status.asStateFlow()
fun selectTask(task: Task) {
_status.update {
val list = it.taskList
val newSelectNumber = list.indexOf(task)
val iterator = list.listIterator()
while (iterator.hasNext()) {
val obj = iterator.next()
if (task.deviceId == obj.deviceId) {
iterator.set(task.copy(selected = true))
} else {
iterator.set(obj.copy(selected = false))
}
}
it.copy(selectedNumber = newSelectNumber)
}
}
}
I modified your selectedTask function, executing _status flow udpates in a single pass using the list's iterator.
Usage somewhere outside (e.g "TaskScreen")
val tasks by stateHolder.status.collectAsState()
Column {
TaskList(tasks.taskList) {
stateHolder.selectTask(it)
}
}

How we can compare textfield values are same value in jetpack compose?

I have register screen in android jetpack compose, and I have ScreenA and ScreenB, in ScreenA I have email and in ScreenB I have again mail and confirm mail, so I want to control those three email values is same value. In ScreenA when I put any mail, in ScreenB both mail must be same mail with ScreenA, any solution?
ScreenA:
#Composable
fun ScreenA(
navController: NavController,
model: MyViewModel
) {
val email = remember { mutableStateOf(TextFieldValue()) }
Column(
Modifier
.fillMaxSize()
,
horizontalAlignment = Alignment.CenterHorizontally
) {
val context = LocalContext.current
OutlinedTextField(
value = emailState.value,
colors = TextFieldDefaults.textFieldColors(
backgroundColor = white,
focusedIndicatorColor = Grey,
unfocusedIndicatorColor = Grey,
focusedLabelColor = Grey,
unfocusedLabelColor = Grey,
cursorColor = custom,
textColor = custom,
),
onValueChange = { emailState.value = it },
label = { Text(text = "Email") },
placeholder = { Text(text = "Email") },
singleLine = true,
modifier = Modifier.fillMaxWidth(0.8f)
)
}
ScreenB:
#Composable
fun ScreenB(
navController: NavController,
model: MyViewModel
) {
val emailState = remember { mutableStateOf(TextFieldValue()) }
val confirmEmailState = remember { mutableStateOf(TextFieldValue()) }
Column(
Modifier.fillMaxSize() ,
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
OutlinedTextField(
value = emailState.value,
colors = TextFieldDefaults.textFieldColors(
backgroundColor = white,
focusedIndicatorColor = Grey,
unfocusedIndicatorColor = Grey,
focusedLabelColor = Grey,
unfocusedLabelColor = Grey,
cursorColor = custom,
textColor = custom,
),
onValueChange = { emailState.value = it },
label = { Text(text = "E-mail") },
singleLine = true,
modifier = Modifier.fillMaxWidth(0.8f)
)
OutlinedTextField(
value = confirmEmailState.value,
colors = TextFieldDefaults.textFieldColors(
backgroundColor = white,
focusedIndicatorColor = Grey,
unfocusedIndicatorColor = Grey,
focusedLabelColor = Grey,
unfocusedLabelColor = Grey,
cursorColor = custom,
textColor = custom,
),
onValueChange = { confirmEmailState.value = it },
label = { Text(text = "Confirm E-mail") },
placeholder = { Text(text = "Confirm E-mail") },
singleLine = true,
modifier = Modifier.fillMaxWidth(0.8f)
)
}
viewmodel:
#HiltViewModel
class MyViewModel #Inject constructor(
val db: FirebaseFirestore,
val auth: FirebaseAuth,
) : ViewModel() {
var singIn = mutableStateOf(false)
var isInProgress = mutableStateOf(false)
var popNotification = mutableStateOf<Event<String>?>(null)
var userData = mutableStateOf<User?>(null)
init {
val currentUser = auth.currentUser
singIn.value = currentUser != null
currentUser?.uid?.let { uid ->
getUserData(uid)
}
}
fun onSignOut() {
auth.signOut()
}
fun onSignUp(
email: String,
password: String
) {
if (
email.isEmpty()
) {
handledException(customMessage = "Please fill in all fields")
return
}
isInProgress.value = true
auth.createUserWithEmailAndPassword(email, password)
.addOnCompleteListener { task ->
if (task.isSuccessful) {
singIn.value = true
} else {
handledException(customMessage = "signed failed")
}
isInProgress.value = false
}
.addOnFailureListener {
}
}
fun onSignUpEmail(
emailState: String,
confirmEmailState: String,
) {
if (
emailState.isEmpty() or
confirmEmailState.isEmpty()
) {
handledException(customMessage = "Please fill in all fields")
return
}
isInProgress.value = true
db.collection(USERS).whereEqualTo("email", email.replace(" ", "")).get()
.addOnSuccessListener { documents ->
if (documents.size() > 0) {
handledException(customMessage = "mail already exist")
isInProgress.value = false
} else {
createOrUpdateProfile(
emailState = emailState,
confirmEmailState = confirmEmailState,
)
isInProgress.value = false
}
}
.addOnFailureListener { }
}
private fun createOrUpdateProfile(
emailState: String? = null,
confirmEmailState: String? = null,
) {
val uid = auth.currentUser?.uid
val userData = User(
emailState = emailState ?: userData.value?.emailState,
confirmEmailState = confirmEmailState ?: userData.value?.confirmEmailState,
)
uid?.let {
isInProgress.value = true
db.collection(USERS).document(uid).get()
.addOnSuccessListener {
if (it.exists()) {
it.reference.update(userData.toMap())
.addOnSuccessListener {
this.userData.value = userData
isInProgress.value = false
}
.addOnFailureListener {
handledException(customMessage = "Cannot Update user")
isInProgress.value = false
}
} else {
db.collection(USERS).document(uid).set(userData)
getUserData(uid)
isInProgress.value = false
}
}
.addOnFailureListener { exception ->
handledException(exception, "Cannot create user")
isInProgress.value = false
}
}
}
private fun getUserData(uid: String) {
isInProgress.value = true
db.collection(USERS).document(uid).get()
.addOnSuccessListener {
val user = it.toObject<User>()
userData.value = user
isInProgress.value = false
}
.addOnFailureListener { exception ->
handledException(exception, "Cannot retrieve user data")
isInProgress.value = false
}
}
fun onLogin(email: String, pass: String) {
if (email.isEmpty() or pass.isEmpty()) {
handledException(customMessage = "Please fill in all fields")
return
}
isInProgress.value = true
auth.signInWithEmailAndPassword(email, pass)
.addOnCompleteListener { task ->
if (task.isSuccessful) {
singIn.value = true
isInProgress.value = false
auth.currentUser?.uid?.let { uid ->
// handledException(customMessage = "Login success")
getUserData(uid)
}
} else {
handledException(task.exception, "Login failed")
isInProgress.value = false
}
}
.addOnFailureListener { exc ->
handledException(exc, "Login failed")
isInProgress.value = false
}
}
private fun handledException(exception: Exception? = null, customMessage: String = "") {
exception?.printStackTrace()
val errorMsg = exception?.message ?: ""
val message = if (customMessage.isEmpty()) {
errorMsg
} else {
"$customMessage: $errorMsg"
}
popNotification.value = Event(message)
}
Move emailState to your viewModel. You can also convert it to Flow, but it is not obligatory.
It looks like that in your viewmodel:
val emailStateA = MutableStateFlow(TextFieldValue())
val emailStateB = MutableStateFlow(TextFieldValue())
val areEmailsEqual = MutableStateFlow(true)
init {
viewModelScope.launch {
combine(emailStateA, emailStateB) { a, b ->
areEmailsEqual.emit(a == b) }.collect()
}
}

How to share HiltViewModel between HorizontalPager and Parent Composable screen that holds that pager?

I have a SignupScreen that holds horizontal pager. The pager has 3 tabs, Register Otp Questions . Those three tabs also update and listen to data from its parent screen's SignupViewModel
These are my code :
#Composable
fun SignupScreen(
navController: NavController
) {
SignupView(navController = navController)
}
#OptIn(ExperimentalPagerApi::class)
#Composable
fun SignupView(
navController: NavController
) {
val vm: SignupViewModel = hiltViewModel()
val signupUiState = vm.signupUiState.value
val scaffoldState = rememberScaffoldState()
val scope = rememberCoroutineScope()
val pagerState = rememberPagerState()
val signupTabs = listOf(
SignupPagerTabs.Register,
SignupPagerTabs.Otp,
SignupPagerTabs.Questions,
)
LaunchedEffect(key1 = true) {
vm.signupUiEvent.collect {
when (it) {
SignupUiEvent.GoNextPage -> {
}
SignupUiEvent.GoPreviousPage -> {
}
SignupUiEvent.NavigateToHomeScreen -> {
navController.popBackStack(AppDestination.Home.route, inclusive = true)
navController.navigate(Routes.HOME_ROUTE)
}
SignupUiEvent.Popup -> {
navController.popBackStack()
}
is SignupUiEvent.ShowSnackBar -> {
scaffoldState.snackbarHostState.showSnackbar(
message = it.message
)
}
}
}
}
Scaffold(
scaffoldState = scaffoldState,
topBar = {
TopBar2(
navIcon = painterResource(id = R.drawable.ic_back),
title = stringResource(id = R.string.top_bar_signup),
onNavIconClicked = {
vm.onEvent(SignupUiAction.ClickPreviousPage)
}
)
},
content = {
SignupContentView(
tabs = signupTabs,
pagerState = pagerState
)
}
)
}
#OptIn(ExperimentalPagerApi::class)
#Composable
fun SignupContentView(
tabs: List<SignupPagerTabs>,
pagerState: PagerState
) {
Column(modifier = Modifier.fillMaxSize()) {
PagerIndicator(tabs.size, pagerState.currentPage)
HorizontalPager(
count = tabs.size,
state = pagerState
) { index ->
tabs[index].screenToLoad()
}
}
}
This is my ViewModel.
#HiltViewModel
class SignupViewModel #Inject constructor(
private val signupUseCase: SignupUseCase
) : ViewModel() {
private val _signupUiState: MutableState<SignupUiState> = mutableStateOf(SignupUiState())
val signupUiState: State<SignupUiState> get() = _signupUiState
private val _signupUiEvent = MutableSharedFlow<SignupUiEvent>()
val signupUiEvent = _signupUiEvent.asSharedFlow()
private val currentPage = signupUiState.value.currentPage
private val completedPage = signupUiState.value.completedPage
fun onEvent(action: SignupUiAction) {
when (action) {
is SignupUiAction.ChangeFullName -> {
_signupUiState.value = signupUiState.value.copy(
fullName = action.name
)
}
is SignupUiAction.ChangePassword -> {
_signupUiState.value = signupUiState.value.copy(
password = action.password
)
}
is SignupUiAction.ChangePasswordConfirm -> {
_signupUiState.value = signupUiState.value.copy(
passwordConfirm = action.passwordConfirm
)
}
is SignupUiAction.ChangePhoneNumber -> {
_signupUiState.value = signupUiState.value.copy(
phoneNumber = action.phone
)
}
is SignupUiAction.ChangeRegion -> {
_signupUiState.value = signupUiState.value.copy(
region = action.country
)
}
SignupUiAction.ClickNextPage -> {
when (currentPage) {
SignupPage.REGISTER_PAGE -> {
_signupUiState.value = signupUiState.value.copy(
currentPage = currentPage + 1
)
}
SignupPage.OTP_PAGE -> {
_signupUiState.value = signupUiState.value.copy(
currentPage = currentPage + 1
)
}
SignupPage.SECURITY_QUESTION_PAGE -> {
//do request to server
viewModelScope.launch {
_signupUiEvent.emit(
SignupUiEvent.NavigateToHomeScreen
)
}
}
}
}
SignupUiAction.ClickPreviousPage -> {
when (currentPage) {
SignupPage.REGISTER_PAGE -> {
viewModelScope.launch {
_signupUiEvent.emit(
SignupUiEvent.Popup
)
}
}
SignupPage.OTP_PAGE -> {
_signupUiState.value = signupUiState.value.copy(
currentPage = currentPage - 1
)
}
SignupPage.SECURITY_QUESTION_PAGE -> {
_signupUiState.value = signupUiState.value.copy(
currentPage = currentPage - 1
)
}
}
}
SignupUiAction.ClickStart -> {
}
SignupUiAction.ClearFullName -> {
_signupUiState.value = signupUiState.value.copy(
fullName = ""
)
}
SignupUiAction.ClearPassword -> {
_signupUiState.value = signupUiState.value.copy(
password = ""
)
}
SignupUiAction.ClearPasswordConfirm -> {
_signupUiState.value = signupUiState.value.copy(
passwordConfirm = ""
)
}
SignupUiAction.ClearPhoneNumber -> {
_signupUiState.value = signupUiState.value.copy(
phoneNumber = ""
)
}
is SignupUiAction.ChangeOtpCode -> {
_signupUiState.value = signupUiState.value.copy(
otpCode = action.code
)
}
SignupUiAction.ClickResend -> {
//todo resend logic
viewModelScope.launch {
}
}
}
}
private fun signup() {
val requestBody = SignupRequest(
countryCodeId = signupUiState.value.region.id,
name = signupUiState.value.fullName,
mobile = signupUiState.value.phoneNumber,
password = signupUiState.value.password,
securityQuestionUsage = listOf()
)
viewModelScope.launch {
signupUseCase(body = requestBody).collect {
when (it) {
is Resource.ErrorEvent -> {
_signupUiEvent.emit(
SignupUiEvent.ShowSnackBar(
message =
it.message ?: "Error"
)
)
}
is Resource.LoadingEvent -> {
_signupUiState.value = signupUiState.value.copy(
isLoading = true,
loadingMessageType = SignupStatus.SECURITY_QUESTION_PAGE
)
}
is Resource.SuccessEvent -> {
}
}
}
}
}
}
This is my Pager Tabs.
object SignupPage {
const val REGISTER_PAGE = 0
const val OTP_PAGE = 1
const val SECURITY_QUESTION_PAGE = 2
}
enum class SignupStatus(val index: Int) {
REGISTER_PAGE(index = SignupPage.REGISTER_PAGE),
OTP_PAGE(index = SignupPage.OTP_PAGE),
SECURITY_QUESTION_PAGE(index = SignupPage.SECURITY_QUESTION_PAGE),
}
sealed class SignupPagerTabs(
val index: Int,
val screenToLoad: #Composable () -> Unit,
) {
object Register : SignupPagerTabs(
index = 0,
screenToLoad = {
RegisterScreen(
)
}
)
object Otp : SignupPagerTabs(
index = 1,
screenToLoad = {
OtpVerificationSignupScreen(
)
}
)
object Questions : SignupPagerTabs(
index = 2,
screenToLoad = {
SecurityQuestionChooseScreen(
)
}
)
}
In Register Otp Question Screens, I want to access the SignupViewModel.
Please Help Me.

Categories

Resources