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()
}
)
}
}
}
}
}
}
}
Related
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.
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.
Trying to make a custom Keyboard using Jetpack Compose. Cannot figure out how call currentInputConnection or other methods from Composable.
#Composable
fun CustomKeyboard() {
var inputVal by remember { mutableStateOf("") }
Column(
modifier = Modifier
.fillMaxSize()
.padding(8.dp)
) {
Spacer(modifier = Modifier.height(50.dp))
Text("Last key pressed: $inputVal")
Row(modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically)
{
MyButton(mText = "A") { inputVal = it}
MyButton(mText = "B") { inputVal = it}
MyButton(mText = "C") { inputVal = it}
}
}
}
#Composable
private fun MyButton(
mText: String,
onPressed: (String) -> Unit
) {
OutlinedButton(
onClick = {
onPressed(mText)
},
modifier = Modifier.padding(4.dp)
) {
Text(text = mText, fontSize = 30.sp, color = Color.White)
}
}
And the InputMethodService class here...
class ComposeKeyboardView(context: Context) : AbstractComposeView(context) {
#Composable
override fun Content() {
CustomKeyboard()
}
}
class IMEService : InputMethodService(), LifecycleOwner, ViewModelStoreOwner,
SavedStateRegistryOwner {
override fun onCreateInputView(): View {
val view = ComposeKeyboardView(this)
window!!.window!!.decorView.let { decorView ->
ViewTreeLifecycleOwner.set(decorView, this)
ViewTreeViewModelStoreOwner.set(decorView, this)
ViewTreeSavedStateRegistryOwner.set(decorView, this)
}
view.let {
ViewTreeLifecycleOwner.set(it, this)
ViewTreeViewModelStoreOwner.set(it, this)
ViewTreeSavedStateRegistryOwner.set(it, this)
}
return view
}
fun doSomethingWith(mData: String) {
currentInputConnection?.commitText(mData, 1)
}
//Lifecylce Methods
private var lifecycleRegistry: LifecycleRegistry = LifecycleRegistry(this)
override fun getLifecycle(): Lifecycle {
return lifecycleRegistry
}
private fun handleLifecycleEvent(event: Lifecycle.Event) =
lifecycleRegistry.handleLifecycleEvent(event)
override fun onCreate() {
super.onCreate()
savedStateRegistry.performRestore(null)
handleLifecycleEvent(Lifecycle.Event.ON_RESUME)
}
override fun onDestroy() {
super.onDestroy()
handleLifecycleEvent(Lifecycle.Event.ON_DESTROY)
}
//ViewModelStore Methods
private val store = ViewModelStore()
override fun getViewModelStore(): ViewModelStore = store
//SaveStateRegestry Methods
private val savedStateRegistry = SavedStateRegistryController.create(this)
override fun getSavedStateRegistry(): SavedStateRegistry = savedStateRegistry.savedStateRegistry
}
If you just need to call a single function, i.e. doSomethingWith(mData: String), or a few of them, then you can pass them into your composables and call them when you want to. This approach would be more loosely coupled and easier to #Preview the CustomKeyboard composable.
#Composable
fun CustomKeyboard(onKeyPressed: (String) -> Unit) {
//...
MyButton(mText = "A") {
inputVal = it
onKeyPressed(it)
}
// ...
}
class ComposeKeyboardView(
context: Context,
private val onKeyPressed: (String) -> Unit,
) : AbstractComposeView(context) {
#Composable
override fun Content() {
CustomKeyboard(onKeyPressed)
}
}
class IMEService : InputMethodService() {
override fun onCreateInputView(): View {
val view = ComposeKeyboardView(this, onKeyPressed = this::doSomethingWith)
// ...
return view
}
private fun doSomethingWith(mData: String) {
currentInputConnection?.commitText(mData, 1)
}
}
If you plan to add many more functions to the IMEService that you will have to also call, then you can just pass the IMEService (or some interface that IMEService implements) into your composables and then call its members normally. Using an interface over an actual class would make it possible to #Preview the CustomKeyboard composable.
#Composable
fun CustomKeyboard(imeService: IMEService) {
//...
MyButton(mText = "A") {
inputVal = it
imeService.doSomethingWith(it)
}
// ...
}
class ComposeKeyboardView(private val imeService: IMEService) : AbstractComposeView(imeService) {
#Composable
override fun Content() {
CustomKeyboard(imeService)
}
}
class IMEService : InputMethodService() {
override fun onCreateInputView(): View {
val view = ComposeKeyboardView(this)
// ...
return view
}
fun doSomethingWith(mData: String) {
currentInputConnection?.commitText(mData, 1)
}
}
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())
}
}
I have read the Android official artical.
I see that MutableStateFlow is hot Flow and is observed by Compose to trigger recomposition when they change.
The Code A is from the the Android official artical, it's OK.
I'm very stranger why the author need to invoke collect to get latest value for Compose UI in Code A.
I think the Compose UI can always get the latest value of latestNewsViewModel.uiState, why can't I use Code B do the the same work?
Code A
class LatestNewsActivity : AppCompatActivity() {
private val latestNewsViewModel = // getViewModel()
override fun onCreate(savedInstanceState: Bundle?) {
...
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
latestNewsViewModel.uiState.collect { uiState ->
when (uiState) {
is LatestNewsUiState.Success -> showFavoriteNews(uiState.news)
is LatestNewsUiState.Error -> showError(uiState.exception)
}
}
}
}
}
}
class LatestNewsViewModel(
private val newsRepository: NewsRepository
) : ViewModel() {
private val _uiState = MutableStateFlow(LatestNewsUiState.Success(emptyList()))
val uiState: StateFlow<LatestNewsUiState> = _uiState
init {
viewModelScope.launch {
newsRepository.favoriteLatestNews
.collect { favoriteNews ->
_uiState.value = LatestNewsUiState.Success(favoriteNews)
}
}
}
}
Code B
class LatestNewsActivity : ComponentActivity() {
private val latestNewsViewModel = // getViewModel()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
SoundMeterTheme {
Surface(color = MaterialTheme.colors.background) {
Greeting(latestNewsViewModel)
}
}
}
}
}
#Composable
fun Greeting(latestNewsViewModel: LatestNewsViewModel) {
val myUIState by remember{ latestNewsViewModel.uiState }
when (myUIState) {
is LatestNewsUiState.Success -> showFavoriteNews(uiState.news)
is LatestNewsUiState.Error -> showError(uiState.exception)
}
}
//The same
Add Content
To RaBaKa 78: Thanks!
By your opinion, can I use Code C instead of Code A?
Code C
class LatestNewsActivity : ComponentActivity() {
private val latestNewsViewModel = // getViewModel()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
SoundMeterTheme {
Surface(color = MaterialTheme.colors.background) {
Greeting(latestNewsViewModel)
}
}
}
}
}
#Composable
fun Greeting(latestNewsViewModel: LatestNewsViewModel) {
val myUIState by remember{ latestNewsViewModel.uiState.collectAsState() }
when (myUIState) {
is LatestNewsUiState.Success -> showFavoriteNews(uiState.news)
is LatestNewsUiState.Error -> showError(uiState.exception)
}
}
//The same
Compose need State not StateFlow to recompose accordingly,
you can easily convert StateFlow to State in compose
val myUiState = latestNewsViewModel.uiState.collectAsState()
There is no need of using a remember {} because your StateFlow is from your viewModel, so it can manage the recomposition without remember
So like CODE B you can manually check the state of the StateFLow or convert to State and automatically recompose when the state changes.
The Code A is XML way of doing things where you can call other functions but in Compose you should do that steps in your viewModel
CODE D
class LatestNewsViewModel(
private val newsRepository: NewsRepository
) : ViewModel() {
private val _uiState = MutableStateFlow(LatestNewsUiState.Success(emptyList()))
val uiState: StateFlow<LatestNewsUiState> = _uiState
init {
viewModelScope.launch {
newsRepository.favoriteLatestNews
.collect { favoriteNews ->
_uiState.value = LatestNewsUiState.Success(favoriteNews)
}
}
}
}
#Composable
fun Greeting(latestNewsViewModel: LatestNewsViewModel) {
val myUIState = latestNewsViewModel.uiState.collectAsState()
Column(modifier = Modifier.fillMaxSIze()) {
when(myUIState) {
is LatestNewsUiState.Success -> SuccessComposable(uiState.news)
is LatestNewsUiState.Error -> showError(uiState.exception) -> ErrorComposable(uiState.exception)
}
}
}