I am trying to use state hoisting in android
I am new to android development using jetpack compose
onSearchChange: (String) -> Unit,
onCategoryChange: (Category) -> Unit,
onProductSelect: (Product) -> Unit,
composable(Screen.Home.route) { MainPage(navController = navController, searchQuery = "",
productCategories = categories, selectedCategory = Category("","",0),
products = pros, /* what do I write here for the 3 lines above?? :( the onSearch,etc I have an error bc of them */
)}
In addition to the answer, apologies, this is a bit long, as Ill try to share how I design my "state hoisting"
Lets simply start first with the following:
A: First based on the Official Docs
State in an app is any value that can change over time. This is a very broad definition and encompasses everything from a Room database to a variable on a class.
All Android apps display state to the user. A few examples of state in Android apps:
- A Snackbar that shows when a network connection can't be established.
- A blog post and associated comments.
- Ripple animations on buttons that play when a user clicks them.
- Stickers that a user can draw on top of an image.
B: And personally, for me
"State Hoisting" is part of "State Management"
Now consider a very simple scenario, We have a LoginForm
with 2 input fields, and have its basic states like the following
userName
password
We have defined 2 requirements above, without doing them, our LoginForm
would be stateless
@Composable
fun LoginForm() {
var userName by remember { mutableStateOf("")}
var password by remember { mutableStateOf("") }
Column(
modifier = Modifier
.fillMaxWidth()
.wrapContentSize()
) {
TextField(
value = userName,
onValueChange = {
userName = it
}
)
TextField(
value = password,
onValueChange = {
password = it
},
visualTransformation = PasswordVisualTransformation()
)
}
}
So far, everything is working but nothing is "Hoisted", their states are handled inside the LoginForm
composable.
State Hoisting Part 1: a LoginState
class
Now apart from the 2 requirements above, lets add one additional requirement.
This can be done inside the LoginForm
composable, but its better to do the logic handling or any business logic in a separate class, leaving your UI intact independent of it
class LoginState {
var userName by mutableStateOf("")
var password by mutableStateOf("")
fun validateAction() {
if (userName == "Stack" && password == "Overflow") {
// tell the ui to show Toast
} else {
// tell the ui to show Toast
}
}
}
@Composable
fun LoginForm() {
val loginState = remember { LoginState() }
Column(
modifier = Modifier
.fillMaxWidth()
.wrapContentSize()
) {
TextField(
value = loginState.userName,
onValueChange = {
loginState.userName = it
}
)
TextField(
value = loginState.password,
onValueChange = {
loginState.password = it
},
visualTransformation = PasswordVisualTransformation()
)
}
}
Now everything is still working and with additional class where we hoisted our userName and password, and we included a validation
functionality, nothing fancy, it will simply call something that will show Toast with a string message depending if the login is valid or not.
State Hoisting Part 2: a LoginViewModel
class
Now apart from the 3 requirements above, lets add some more realistic requirements
Post
login network
call and update your database
Take note that the codes below won't simply work and not how you would define it in a real situation though.
val viewModel = LoginViewModel()
data class UserLogin(
val userName : String = "",
val password : String = ""
)
class LoginViewModel (
val loginRepository: LoginRepository
) {
private val _loginFlow = MutableStateFlow(UserLogin())
val loginFlow : StateFlow<UserLogin> = _loginFlow
fun validateAction() {
// ommited codes
}
fun onUserNameInput(userName: String) {
}
fun onPasswordInput(password: String) {
}
}
@Composable
fun LoginForm() {
val loginState by viewModel.loginFlow.collectAsStateWithLifecycle()
Column(
modifier = Modifier
.fillMaxWidth()
.wrapContentSize()
) {
TextField(
value = loginState.userName,
onValueChange = {
viewModel.onUserNameInput(it)
}
)
TextField(
value = loginState.password,
onValueChange = {
viewModel.onPasswordInput(it)
},
visualTransformation = PasswordVisualTransformation()
)
}
}
But that's the most top level state hoisting you can do where you would deal with network calls and database.
To summarize:
State Class
like the LoginState
class to make your UI independent of the business logic.LifeCycle
, consider using a ViewModel
Another thing to mention but out of topic is when you are hoisting states, there is a thing called scoped re-composition
where you want a specific composable to get updated without affecting the others around, it is where you will think your composable designs on how you would handle mutableStates.