How do I cancel an event after another one has started? - android

I am having an issue, where after a Snackbar appears the FAB won't perform navigation for the period of the Snackbar message. If I click it multiple times when the Snackbar is displayed it will stack and open multiple instances of one screen (only after the Snackbar is gone). How do I cancel showing the Snackbar on click of the FAB and preserve the functionality of the FAB at all times?
TasksList.kt:
#OptIn(ExperimentalMaterial3Api::class)
#Composable
fun TasksListScreen(
modifier: Modifier = Modifier,
onNavigate: (UiEvent.Navigate) -> Unit,
viewModel: TaskListViewModel = hiltViewModel()
) {
val tasks = viewModel.tasks.collectAsState(initial = emptyList())
val snackbarHostState = remember { SnackbarHostState() }
LaunchedEffect(key1 = true) {
viewModel.uiEvent.collect { event ->
when (event) {
is UiEvent.ShowSnackbar -> {
val result = snackbarHostState.showSnackbar(
message = event.message,
actionLabel = event.action,
duration = SnackbarDuration.Long,
)
if (result == SnackbarResult.ActionPerformed) {
viewModel.onEvent(TaskListEvent.OnUndoDeleteTask)
}
}
is UiEvent.Navigate -> onNavigate(event)
else -> Unit
}
}
}
Scaffold(
snackbarHost = {
SnackbarHost(snackbarHostState) { data ->
Snackbar(
shape = RoundedShapes.medium,
actionColor = MaterialTheme.colorScheme.primary,
contentColor = MaterialTheme.colorScheme.background,
snackbarData = data
)
}
},
floatingActionButton = {
FloatingActionButton(
shape = RoundedShapes.medium,
onClick = { viewModel.onEvent(TaskListEvent.OnAddTask) },
containerColor = MaterialTheme.colorScheme.primary,
contentColor = MaterialTheme.colorScheme.background
) {
Icon(
imageVector = Icons.Default.Add,
contentDescription = stringResource(R.string.fab_cd)
)
}
},
topBar = {
TopAppBar(
title = { Text(stringResource(R.string.app_name)) },
colors = TopAppBarDefaults.topAppBarColors(
containerColor = MaterialTheme.colorScheme.primary,
titleContentColor = MaterialTheme.colorScheme.background
)
)
},
) { padding ->
LazyColumn(
state = rememberLazyListState(),
verticalArrangement = spacedBy(12.dp),
contentPadding = PaddingValues(vertical = 16.dp),
modifier = modifier
.fillMaxSize()
.padding(padding)
.padding(horizontal = 16.dp),
) {
items(items = tasks.value, key = { task -> task.hashCode() }) { task ->
val currentTask by rememberUpdatedState(newValue = task)
val dismissState = rememberDismissState(confirmValueChange = {
if (it == DismissValue.DismissedToStart) {
viewModel.onEvent(TaskListEvent.OnDeleteTask(currentTask))
}
true
})
SwipeToDismiss(state = dismissState,
directions = setOf(DismissDirection.EndToStart),
background = { },
dismissContent = {
TaskItem(
task = task, modifier = modifier
)
})
}
}
}
}
TaskListViewModel:
#HiltViewModel
class TaskListViewModel #Inject constructor(private val repository: TaskRepositoryImplementation) :
ViewModel() {
val tasks = repository.getTasks()
private val _uiEvent = Channel<UiEvent>()
val uiEvent = _uiEvent.receiveAsFlow()
private var deletedTask: Task? = null
fun onEvent(event: TaskListEvent) {
when (event) {
is TaskListEvent.OnAddTask -> {
sendUiEvent(UiEvent.Navigate(Routes.ADD_EDIT_TASK))
}
is TaskListEvent.OnEditClick -> {
sendUiEvent(UiEvent.Navigate(Routes.ADD_EDIT_TASK + "?taskId=${event.task.id}"))
}
is TaskListEvent.OnDeleteTask -> {
val context = TaskApp.instance?.context
viewModelScope.launch(Dispatchers.IO) {
deletedTask = event.task
repository.deleteTask(event.task)
if (context != null) {
sendUiEvent(
UiEvent.ShowSnackbar(
message = context.getString(R.string.snackbar_deleted),
action = context.getString(R.string.snackbar_action)
)
)
}
}
}
is TaskListEvent.OnUndoDeleteTask -> {
deletedTask?.let { task ->
viewModelScope.launch {
repository.addTask(task)
}
}
}
}
}
private fun sendUiEvent(event: UiEvent) {
viewModelScope.launch(Dispatchers.IO) {
_uiEvent.send(event)
}
}
}

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

Jetpack Compose Navigation calls navigate function repeatedly and freezes

Using a when function, I need to navigate to particular graph in my compose navigation tree. The issue is when navigating away from the current screen to the new one, the screen repeatedly flashes and freezes up. Checking the debugger, it shows me that the code just constantly jumps between LaunchedEffect and navController.navigate(ProDashboardScreens.ProDashboardGraph.route).
Can anyone see a problem in my set up?
composable(BasicDashboardScreens.Manager.route) {
val managerViewModel: ManagerViewModel = hiltViewModel()
val managerState by managerViewModel.state.collectAsState()
ManagerScreen(managerState, managerViewModel)
LaunchedEffect(key1 = managerViewModel.navigationEvent) {
managerViewModel.navigationEvent.collect {
when (it) {
AuthenticationScreens.Login -> {
navController.navigate(AuthenticationScreens.Login.route)
}
AuthenticationScreens.Register -> {
navController.navigate(AuthenticationScreens.Register.route)
}
ProDashboardScreens.ProDashboardGraph -> {
navController.navigate(ProDashboardScreens.ProDashboardGraph.route)
}
else -> Unit
}
}
}
}
when {
!state.userAuthenticated -> {
UnauthenticatedScreen(
title = {
Text(
text = stringResource(id = R.string.login_or_register),
style = MaterialTheme.typography.subtitle1.copy(
fontWeight = FontWeight.Bold
),
textAlign = TextAlign.Center
)
},
buttonOne = {
AppButton(
modifier = Modifier
.fillMaxWidth()
.height(64.dp),
buttonText = stringResource(id = R.string.login),
buttonColor = Color.Blue,
enabled = true,
onClick = {
events.loginClicked()
}
)
},
buttonTwo = {
AppButton(
modifier = Modifier
.fillMaxWidth()
.height(64.dp),
buttonText = stringResource(id = R.string.register),
buttonColor = Color.Blue,
enabled = true,
onClick = {
events.registerClicked()
}
)
}
)
}
state.userAuthenticated && !state.userProfile.proVersion -> {
UnauthenticatedScreen(
title = {
Text(
text = stringResource(id = R.string.purchase_pro_enable_access),
style = MaterialTheme.typography.subtitle1.copy(
fontWeight = FontWeight.Bold
),
textAlign = TextAlign.Center
)
},
buttonOne = {
AppButton(
modifier = Modifier
.fillMaxWidth()
.height(64.dp),
buttonText = stringResource(id = R.string.purchase_pro),
buttonColor = Color.Blue,
enabled = true,
onClick = {
events.purchaseProClicked()
}
)
}
)
}
else -> {
events.navigateToProGraph()
}
}
#HiltViewModel
class ManagerViewModel #Inject constructor(
private val userProfileDao: UserProfileDao,
private val authManager: AuthManager
) : ViewModel(), ManagerEvents {
private val _state = MutableStateFlow(ManagerState.defaultState)
val state: StateFlow<ManagerState> = _state
private val _navigationEvent = MutableSharedFlow<NavigationRoute>()
val navigationEvent: SharedFlow<NavigationRoute> = _navigationEvent
init {
viewModelScope.launch {
_state.value = state.value.copy(
userProfile = userProfileDao.getUserProfile().first() ?: UserProfile.defaultState,
userAuthenticated = authManager.isAuthenticated()
)
}
}
override fun loginClicked() {
viewModelScope.launch {
_navigationEvent.emit(AuthenticationScreens.Login)
}
}
override fun registerClicked() {
viewModelScope.launch {
_navigationEvent.emit(AuthenticationScreens.Register)
}
}
override fun navigateToProGraph() {
viewModelScope.launch {
_navigationEvent.emit(ProDashboardScreens.ProDashboardGraph)
}
}
}

How to enable Compose snackbar's swipe-to-dismiss behavior

Simple code below for showing the Compose Snackbar
This code correctly shows the Snackbar, when onClick event occurs.
val scaffoldState = rememberScaffoldState() // this contains the `SnackbarHostState`
val coroutineScope = rememberCoroutineScope()
Scaffold(
modifier = Modifier,
scaffoldState = scaffoldState // attaching `scaffoldState` to the `Scaffold`
) {
Button(
onClick = {
coroutineScope.launch { // using the `coroutineScope` to `launch` showing the snackbar
// taking the `snackbarHostState` from the attached `scaffoldState`
val snackbarResult = scaffoldState.snackbarHostState.showSnackbar(
message = "This is your message",
actionLabel = "Do something."
)
when (snackbarResult) {
SnackbarResult.Dismissed -> Log.d("SnackbarDemo", "Dismissed")
SnackbarResult.ActionPerformed -> Log.d("SnackbarDemo", "Snackbar's button clicked")
}
}
}
) {
Text(text = "A button that shows a Snackbar")
}
}
How to dismiss snackbar on right/left-swipe?
The SnackbarHost has no such functionality. But you can extend it with the nackbarHost argument.
Also if you want the snackbar to disappear only with a swipe, you probably need to set the duration to Indefinite:
Scaffold(
modifier = Modifier,
scaffoldState = scaffoldState,
snackbarHost = { SwipeableSnackbarHost(it) } // modification 1
) {
Button(
onClick = {
coroutineScope.launch {
val snackbarResult = scaffoldState.snackbarHostState.showSnackbar(
message = "This is your message",
actionLabel = "Do something.",
duration = SnackbarDuration.Indefinite, // modification 2
)
when (snackbarResult) {
SnackbarResult.Dismissed -> Log.d("SnackbarDemo", "Dismissed")
SnackbarResult.ActionPerformed -> Log.d(
"SnackbarDemo",
"Snackbar's button clicked"
)
}
}
}
) {
Text(text = "A button that shows a Snackbar")
}
}
SwipeableSnackbarHost inspired by this answer
enum class SwipeDirection {
Left,
Initial,
Right,
}
#Composable
fun SwipeableSnackbarHost(hostState: SnackbarHostState) {
if (hostState.currentSnackbarData == null) { return }
var size by remember { mutableStateOf(Size.Zero) }
val swipeableState = rememberSwipeableState(SwipeDirection.Initial)
val width = remember(size) {
if (size.width == 0f) {
1f
} else {
size.width
}
}
if (swipeableState.isAnimationRunning) {
DisposableEffect(Unit) {
onDispose {
when (swipeableState.currentValue) {
SwipeDirection.Right,
SwipeDirection.Left -> {
hostState.currentSnackbarData?.dismiss()
}
else -> {
return#onDispose
}
}
}
}
}
val offset = with(LocalDensity.current) {
swipeableState.offset.value.toDp()
}
SnackbarHost(
hostState,
snackbar = { snackbarData ->
Snackbar(
snackbarData,
modifier = Modifier.offset(x = offset)
)
},
modifier = Modifier
.onSizeChanged { size = Size(it.width.toFloat(), it.height.toFloat()) }
.swipeable(
state = swipeableState,
anchors = mapOf(
-width to SwipeDirection.Left,
0f to SwipeDirection.Initial,
width to SwipeDirection.Right,
),
thresholds = { _, _ -> FractionalThreshold(0.3f) },
orientation = Orientation.Horizontal
)
)
}

Why my lazyColumn is only loading when I go to a different fragment and back in Android?

I have this list I am getting from Firebase and displaying in a LazyColumn but it only works when I navigate to a different fragment and press back. My lazyColumn is being composed inside de ScreenController for the bottomNavigation.
What do I have to do to display it directly?
below is my Fragment
#AndroidEntryPoint
class OpenTicketFragment : Fragment() {
private val viewModel: OpenTicketsViewModel by viewModels()
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
return ComposeView(requireContext()).apply {
setContent {
val ticketsList = viewModel.tickets.value.data
val currentUserId = viewModel.currentUserId.value
TheProjectTheme {
val navController = rememberNavController()
val title = remember { mutableStateOf("Open Tickets") }
val isDialogOpen = remember { mutableStateOf(false) }
Scaffold(
topBar = {
TopAppBar(
navigationIcon = {
IconButton(onClick = {
isDialogOpen.value = true
}) {
Icon(Icons.Default.ExitToApp, contentDescription = null)
}
},
title = {
Text(text = title.value)
},
actions = {
IconButton(onClick = {
findNavController().navigate(R.id.action_openTicketFragment_to_profileFragment)
}) {
Icon(Icons.Default.Person, contentDescription = null)
}
}
)
},
bottomBar = {
val items = listOf(
Screen.Open,
Screen.Available,
Screen.Closed
)
BottomNavigation {
val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentRoute =
navBackStackEntry?.arguments?.getString(KEY_ROUTE)
items.forEach {
BottomNavigationItem(
icon = { Icon(it.icon, contentDescription = null) },
selected = currentRoute == it.route,
label = { Text(text = it.label) },
onClick = {
navController.popBackStack(
navController.graph.startDestination, false
)
if (currentRoute != it.route) {
navController.navigate(it.route)
}
})
}
}
}
)
{
ScreenController(
navHostController = navController,
title,
ticketsList!!,
findNavController(),
currentUserId
)
AlertDialogComponent(isDialogOpen, findNavController(), viewModel)
}
}
}
}
}
}
#Composable
fun OpenTicketLazyColumn(
ticket: Ticket,
// onClick: () -> Unit,
navController: NavController,
) {
Card(
modifier = Modifier
.padding(8.dp)
.clickable {
val action =
OpenTicketFragmentDirections.actionOpenTicketFragmentToTicketDetailFragment(
ticket.ticketId,
ticket.problem,
ticket.address,
ticket.dateOpened,
ticket.description,
ticket.name,
ticket.status
)
Log.d("SSS8", ticket.ticketId)
navController.navigate(action)
},
elevation = 4.dp
) {
Row(
modifier = Modifier
.padding(8.dp)
.fillMaxWidth()
) {
Column(
modifier = Modifier
.padding(8.dp)
.weight(1f)
) {
Text(
text = "Ticket Opened Date: ${ticket.dateOpened}",
fontSize = 16.sp,
)
Text(
text = "Service required: ${ticket.problem}",
fontSize = 14.sp,
)
}
Column(
modifier = Modifier
.padding(8.dp)
.weight(1f)
) {
Text(
text = "Status: ${ticket.status}",
fontSize = 16.sp,
)
Text(
text = "Address: ${ticket.address}",
fontSize = 16.sp,
)
}
}
}
}
and my composables
#Composable
fun ScreenController(
navHostController: NavHostController,
topBarTitle: MutableState<String>,
ticketList: List<Ticket>,
navController: NavController,
currentUserId: String
) {
NavHost(
navController = navHostController, startDestination = "open"
) {
composable("open") {
Log.d("SACO", currentUserId)
LazyColumn(
modifier = Modifier
.padding(8.dp)
) {
items(items = ticketList) { item ->
val status = remember { mutableStateOf(item.status)}
if (status.value == ASSIGNED && item.assignedToId == currentUserId) {
OpenTickets(
item,
navController
)
}
}
}
topBarTitle.value = "Open Tickets"
}
composable("available") {
LazyColumn(
modifier = Modifier
.padding(8.dp)
) {
items(items = ticketList) { item ->
val status = remember { mutableStateOf(item.status)}
if (status.value == OPEN) {
ClosedTickets(
item,
navController
)
}
}
}
topBarTitle.value = "Available Tickets"
}
composable("closed") {
LazyColumn(
modifier = Modifier
.padding(8.dp)
) {
items(items = ticketList) { item ->
val status = remember { mutableStateOf(item.status)}
if (status.value == CLOSED && item.assignedToId == currentUserId) {
AvailableTickets(
item,
navController
)
}
}
}
topBarTitle.value = "Closed Tickets"
}
}
}
#Composable
fun OpenTickets(
ticket: Ticket,
navController: NavController
) {
OpenTicketLazyColumn(
ticket,
navController
)
}
the viewModel
#HiltViewModel
class OpenTicketsViewModel #Inject constructor(
private val repository: WorkerRepositoryImpl,
private val firesource: FireBaseSource
) : ViewModel() {
val currentUserId = mutableStateOf("")
val tickets: MutableState<DataOrException<List<Ticket>, Exception>> = mutableStateOf(
DataOrException(
listOf(),
Exception("")
)
)
init {
getAllTickets(listOfServices)
}
private fun getAllTickets() {
viewModelScope.launch {
val ticketsList = repository.getAllTickets().data
tickets.value.data = ticketsList
}
}
the firebaseSource
class FireBaseSource #Inject constructor(
private val firebaseAuth: FirebaseAuth,
private val firestore: FirebaseFirestore
) {
suspend fun getAllTickets(
listOfServices: List<String>
): DataOrException<List<Ticket>, Exception> {
val dataOrException = DataOrException<List<Ticket>, Exception>()
try {
dataOrException.data = firestore.collection(TICKETS)
.whereEqualTo(PROBLEM, ELECTRICIAN)
.get()
.await().map { document ->
document.toObject(Ticket::class.java)
}
} catch (e: FirebaseFirestoreException) {
dataOrException.e = e
}
return dataOrException
}
}
1. List item

Categories

Resources