swipe to refresh using accompanist - android

I'm using accompanist library for swipe to refresh.
And I adopt it sample code for testing, however, it didn't work.
I search for adopt it, but I couldn't find.
Is there anything wrong in my code?
I want to swipe when user needs to refresh
class MyViewModel : ViewModel() {
private val _isRefreshing = MutableStateFlow(false)
val isRefreshing: StateFlow<Boolean>
get() = _isRefreshing.asStateFlow()
fun refresh() {
// This doesn't handle multiple 'refreshing' tasks, don't use this
viewModelScope.launch {
// A fake 2 second 'refresh'
_isRefreshing.emit(true)
delay(2000)
_isRefreshing.emit(false)
}
}
}
#Composable
fun SwipeRefreshSample() {
val viewModel: MyViewModel = viewModel()
val isRefreshing by viewModel.isRefreshing.collectAsState()
SwipeRefresh(
state = rememberSwipeRefreshState(isRefreshing),
onRefresh = { viewModel.refresh() },
) {
LazyColumn {
items(30) { index ->
// TODO: list items
}
}
}
}
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE)
setContent {
TestTheme {
// A surface container using the 'background' color from the theme
Surface(color = MaterialTheme.colors.background) {
}
}
}
}
}

Your list doesn't take up the full screen width and you should include the state parameter:
SwipeRefresh(
state = rememberSwipeRefreshState(isRefreshing),
onRefresh = { viewModel.refresh() },
) {
LazyColumn(state = rememberLazyListState(), modifier = Modifier.fillMaxSize()) {
items(100) { index ->
Text(index.toString())
}
}
}
or with Column:
Column(modifier = Modifier
.fillMaxSize()
.verticalScroll(rememberScrollState())) {
repeat(100) { index ->
Text(index.toString())
}
}

Related

Datastore does not update mutableStateOf to update the view

I'm using DataStore to hold a boolean and depending on the value the view will change. The value does change and works sort of, it is just the app requires a restart for the change to take. Is this because of the runBlocking?
This is the suspend fun I'm calling
suspend fun isOnBoardingCompleted(): Boolean {
return context
.dataStore
.data
.map { preferences -> preferences[ON_BOARDING_COMPLETED] ?: false }
.first()
}
class MainActivity : ComponentActivity() {
private val projectViewModel: ProjectRoomViewModel by viewModels()
#OptIn(ExperimentalPagerApi::class)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val projectRoomViewModel: ProjectRoomViewModel by viewModels()
setContent {
val preferences = DataStoreViewModel(this)
val isOnBoardingCompleted = remember {
mutableStateOf(
runBlocking {
preferences.isOnBoardingCompleted()
}
)
}
AppTheme {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colors.background
) {
if (isOnBoardingCompleted.value) {
RootScreen( preferences = preferences, projectRoomViewModel = projectRoomViewModel)
} else {
PagerView(navController = rememberNavController()) {
mutableStateOf(
runBlocking{
preferences.disableOnboarding()
}
)
}
}
}
}
}
}
}

How to recompose a composable after an event occured in the main Activity?

I created an event listener to catch when a physical button is pressed, and it works well.
But I would want to update a list used in a LazyColumn
class MainActivity : ComponentActivity() {
#OptIn(ExperimentalComposeUiApi::class)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
Theme {
Surface(
modifier = Modifier .fillMaxSize(),
color = MaterialTheme.colors.background
) {
Column(modifier = Modifier.fillMaxSize()) {
Greeting("Android")
}
}
}
}
}
#SuppressLint("RestrictedApi")
override fun dispatchKeyEvent(event: KeyEvent?): Boolean {
// Handling event to get a text (type String)
// ......
//then updating my list
myList+=newValue
}
var myList: List<String> = mutableListOf()
#OptIn(ExperimentalFoundationApi::class, ExperimentalComposeUiApi::class)
#Composable
fun Greeting(name: String, paramBarcode: String) {
var mutableList by remember {mutableStateOf(myList)}
Button(onClick = {
myList+= "new item"
mutableList = myList
}) {
Text(text = "Add")
}
LazyColumn(Modifier.fillMaxSize() .padding(16.dp)
) {
stickyHeader {Row(Modifier.fillMaxSize() .background(Color.Green)
) {
TableCell(text = "Code", width = 264)
}
}
itemsIndexed(items = mutableList, itemContent = {
index, item ->
Row(Modifier.fillMaxSize(),
) {
TableCell(text = item, width = 256)
}
})
}
}
If I try to add or remove an element of the list from my composable, everything is fine, but I can't get the same behaviour from my event.
I also tried to pass the list as a parameter to my composable, but it didn't help at all.
Try using a SnapshotStateList instead of an ordinary list as mutable state.
So instead of this
var myList: List<String> = mutableListOf()
try this,
var myList = mutableStateListOf("Item1")
and instead of using your ordinary list setting it with a new one every time you add an item, you can simply add new elements to a SnapshotStateList.
I modified your code and any changes coming from outside Greeting and inside of it reflects to LazyColumn
#Composable
fun Greeting(
list: SnapshotStateList<String>
) {
Button(onClick = {
list.add("Item inside greeting")
}) {
Text(text = "Add")
}
LazyColumn(
Modifier
.fillMaxSize()
.padding(16.dp)
) { ... }
}
Usage
setContent {
Column {
Button(onClick = {
myList.add("New Item Outside greeting")
}) {}
Greeting(myList)
}
}
I've got a very good answer from #x.y
but then it lost value at recomposition.
Here is the updated code to handle my first problem while keeping the state of the list :
var MainList: SnapshotStateList<String> = SnapshotStateList()
class MainActivity : ComponentActivity() {
var myList: SnapshotStateList<String> = MainList
#OptIn(ExperimentalComposeUiApi::class)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
Theme {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colors.background
) {
Column(modifier = Modifier.fillMaxSize()) {
Greeting("Android", myList)
}
}
}
}
}
}
#SuppressLint("RestrictedApi")
override fun dispatchKeyEvent(event: KeyEvent?): Boolean {
MainList.add("$barcode")
}
}
#OptIn(ExperimentalFoundationApi::class, ExperimentalComposeUiApi::class)
#Composable
fun Greeting(name: String, paramBarcode: String, theList: SnapshotStateList<ScanItem>) {
var mutableList by remember { mutableStateOf(theList) }
Column() {
Button(onClick = {
MainList.add("ABC0000000000001")
}) {
Text(text = "Add item")
}
Row() {
Button(onClick = {
MainList.add("add00001")
}) {
Text(text = "Add")
}
Button(
onClick = { MainList.clear() }
) {
Text("Empty list")
}
}
}
}
And I can pass mutableList as the parameter in itemsIndexed, and it will keep the state on recompose.

Compose navigation lose state after pop screen (navigate for network success)

I am using compose navigation with single activity and no fragments.
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MobileComposePlaygroundTheme {
Surface(color = MaterialTheme.colors.background) {
val navController = rememberNavController()
NavHost(navController, startDestination = "main") {
composable("main") { MainScreen(navController) }
composable("helloScreen/{data}") { HelloScreen() }
}
}
}
}
}
}
#Composable
private fun MainScreen(navController: NavHostController) {
val viewModel = viewModel()
val loginState by viewModel.loginState
LaunchedEffect(loginState) {
if(loginState is State.Success){
navController.navigate("helloScreen/data")
}
}
Column {
Spacer(modifier = Modifier.height(16.dp))
Button(
onClick = { viewModel.login() }, //viewmodel will change loginState
modifier = Modifier.padding(8.dp)
) {
Text(text = "Go To HelloScreen")
}
}
}
#Composable
fun HelloScreen() {
Log.d("TAG", "HelloScreen")
Text("Hello Screen")
}
This post is hidden. You deleted this post 7 mins ago.
i have some problem
MainScreen(loginState)-> LaunchedEffect(loginState) -> HelloScreen -> back button -> MainScreen
when i pop HelloScreen
MainScreen will be recompose for
loginState, LaunchedEffect(netWorkState) will navigate(HelloScreen) again
how can i change code right of navigate
Use Disposable effect
DisposableEffect(loginState) {
if(loginState is State.Success){
navController.navigate("helloScreen/data")
}
onDispose {
loginState = State.Idle // Reset back your state here
}
}

CountDownTimer in Android ViewModel executes onFinish twice

I tried to implement a timer in a ViewModel that after ten seconds returns to the previous view, but it doesn't work at all. I wonder if CountDownTimer can even be used in a ViewModel.
class PairingScreenViewModel(
private val routing: NavController,
) : ViewModel() {
var content = mutableStateOf(10)
var counter: CountDownTimer? = null
init {
viewModelScope.launch {
delay(1000L)
counter = object : CountDownTimer(9000L, 1000L) {
override fun onTick(millisUntilFinished: Long) {
content.value = content.value.dec()
}
override fun onFinish() {
routing.popBackStack() // Go back twice. WHY?
}
}.start()
}
}
override fun onCleared() {
super.onCleared() // It seems that it never runs.
counter?.cancel()
}
fun onButtonClicked() = viewModelScope.launch {
counter?.cancel() // It doesn't cancel the timer.
routing.navigate("next-route")
}
}
EDIT: This is my MainActivity.kt, I am probably doing something wrong with NavHostController.
This is the first time I use androidx.navigation:navigation-compose.
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
val routing = rememberNavController()
val heading = "MyApplication"
AndroidNavigationDemoTheme {
Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colors.background) {
Scaffold(
topBar = {
TopAppBar(
title = { Text(heading, textAlign = TextAlign.Center, modifier = Modifier.fillMaxWidth()) }
)
},
content = {
Router(routing)
}
)
}
}
}
}
}
#Composable
fun Router(routing: NavHostController) {
NavHost(
navController = routing,
startDestination = "welcome"
) {
composable("welcome") {
WelcomeScreen(routing)
}
composable("hisense") {
HisenseScreen(HisenseScreenViewModel(routing))
}
composable("pairing") {
PairingScreen(PairingScreenViewModel(routing))
}
// ...
}
}
I have to remove ViewModel from the NavHost.
#Composable
fun Router(routing: NavHostController) {
NavHost(
navController = routing,
startDestination = "welcome"
) {
composable("welcome") {
WelcomeScreen(routing)
}
// ...
composable("pairing") {
PairingScreen(routing)
}
// ..
}
}
And set the ViewModel from the composable.
class PairingScreenViewModelFactory(private val routing: NavController) :
ViewModelProvider.NewInstanceFactory() {
override fun <T : ViewModel> create(modelClass: Class<T>): T = PairingScreenViewModel(routing) as T
}
#Composable
fun PairingScreen(routing: NavController) {
val viewModel: PairingScreenViewModel = viewModel(factory = PairingScreenViewModelFactory(routing))
// ...
}
And everything is working like a charm.

How could i run my logic code inside composable function just 1 time?

I'm doing my test project using ViewModel and ComposeView.
My architecture include: one Activity and multi ComposeView, using navigation like this:
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MainView()
}
}
}
#Composable
fun MainView() {
TestComposeTheme {
val navController = rememberNavController()
Surface(color = MaterialTheme.colors.background) {
NavHost(
navController = navController,
startDestination = KYCScreen.getName(),
) {
navigation(startDestination = KYCScreen.Preload.name, route = KYCScreen.getName()) {
composable(KYCScreen.Preload.name) { PreloadView(navHostController = navController) }
composable(KYCScreen.StartKYC.name) { StartView(navHostController = navController) }
composable(KYCScreen.Login.name) { LoginView(navHostController = navController) }
}
navigation(startDestination = HomeScreen.Home.name, route = HomeScreen.getName()) {
composable(HomeScreen.Home.name) { HomeView(navHostController = navController) }
}
}
}
}
}
The problem happens when I include logic that executes when a Composable function is executed, my logic code was looped many times. Here my code:
#Composable
fun PreloadView(navHostController: NavHostController) {
val preloadViewModel: PreloadViewModel = viewModel()
preloadViewModel.getSyncData()
val syncState by preloadViewModel.uiStateSync.observeAsState()
syncState?.let { PreloadContent(navHostController = navHostController, it) }
}
#Composable
fun PreloadContent(navHostController: NavHostController, uiState: UiState<SyncResponse>) {
when (uiState.state) {
RequestState.SUCCESS -> {
navHostController.navigate(KYCScreen.StartKYC.name)
}
RequestState.FAIL -> {
Box(
contentAlignment = Alignment.Center,
modifier = Modifier
.fillMaxHeight()
.fillMaxWidth()
) {
Text(text = "Error", color = Color.Black)
}
}
RequestState.NON -> {
}
}
}
Anyone have a solution to help me with that architecture?
Your composable function should be side-effects free.
If you want to run something just once. You can do something like the following
LaunchedEffect(Unit){
preloadViewModel.getSyncData()
}
or in your case, if it is mandatory to Sync Data when ViewModel initializes you can call this function in the init block inside of your ViewModel.
Check the official doc https://developer.android.com/jetpack/compose/side-effects

Categories

Resources