android 在Jetpack Compose上处理状态的最佳方法是什么/在哪里?

g6baxovj  于 2022-12-09  发布在  Android
关注(0)|答案(2)|浏览(171)

我看过一些Jetpack Compose项目,我看到了两种类型的管理状态,不知道哪一种更好。
例如,让我们假设:输入状态。我看到人们在UI中管理这个状态,使用remember来保存值的状态。
另一种方法是在ViewModel中创建这个mutableState,然后在那里存储/使用它。

ssm49v7z

ssm49v7z1#

In addition to @Thracian's answer.
Let me share my thought process based on my current level of experience in Jetpack Compose. Just a disclaimer, I'm still in the learning curve.
IMO, theres no such thing as "best", things in our field evolves, what might be considered "best" today may become obsolete tomorrow, but there are certain practices that are "recommended", approved and adopted by the community which might save you from dealing with some pitfalls (e.g unwanted re-compositions, infinite navhost calls( you already dealt with this) etc..), but its up to you if you will follow it or not.
So what your'e trying to understand is called State Hoisting . The way I could explain this is by just simply sampling a scenario (again this is based on my own experience with how I apply my knowledge in Jetpack Compose).
Consider a Login use-case with 3 different levels of complexity

  • A Login UI prototype : — Just showcasing your potential Login Screen design and user interaction
  • Login UI Mock-up : — With a bit of validation and some toast showing a negative scenario, just an advance version of the prototype
  • A fully working Login module — where you have to construct view models, bind things to lifecycles, perform concurrent operations etc..

At this point, you already have an idea the different levels of state management based on the use-case above.
For a Login prototype, I won't be needing a state class or a view model, since its just a prototype

@Composable
fun LoginScreen() {
    
    val userName by remember { <mutable string state username> }
    val password by remember { <mutable string state password> }

    Column {
        Text(text = username)
        Text(text = password)

        Button("Login")
    }
}

and because its a very simple UI(composable), I only need to specify basic structure of a composable using remember + state, showcasing an input is happening.
For the Login mock-up with simple validation, we utilized the recommended state hoisting using a class,

class LoginState {

    var event;
    var mutableUserNameState;
    var mutablePasswordState;

    fun onUserNameInput() {...}
    fun onPasswordInput() {...}

    fun onValidate() {
        if (not valid) {
            event.emit(ShowToast("Not Valid"))
        } else {
            event.emit(ShowToast("Valid"))
        }
    }
}

@Composable
fun LoginScreen() {

    val loginState by remember { LoginState }

    LaunchedEffect() {
        event.observe {
            it.ShowToast()
        }
    }
    Column {
        Text(text = loginState.mutableUserNameState, onInput = { loginState.onUserNameInput()} )
        Text(text = loginState.mutablePasswordState, onInput = { loginState.onPasswordInput()} )

        Button(loginState.onValidate)
    }
}

Now for a full blown Login Module, where your'e also taking lifecylce scopes into consideration

class LoginViewModel(
    val userRepository: UserRepository // injected by your D.I framework
): ViewModel {

    var event;
    var mutableUserNameState;
    var mutablePasswordState;

    fun onUserNameInput() {...}
    fun onPasswordInput() {...}

    fun onValidateViaNetwork() {
        // do a non-blocking call to a server
        viewModelScope.launch {
            var isUserValid = userRepository.validate(username, password)
            if (isUserValid) {
                event.emit(ShowToast("Valid"))
            } else {
                event.emit(ShowToast("Not Valid"))
            }
        }
    }
}

@Composable
fun LoginScreen() {

    val userNameState by viewModel.mutableUserNameState
    val passwordState by viewModel.mutablePasswordState
    
    LaunchedEffect() {
        event.observe {
            it.ShowToast()
        }
    }

    Column {
        Text(text = userNameState, onInput = { viewModel.onUserNameInput()} )
        Text(text = passwordState, onInput = { viewModel.onPasswordInput()} )

        Button(viewModel.onValidateViaNetwork)
    }
}

Again, this is just based on my experience and how I decide on hoisting my states. As for the snippets I included, I tried to make them as pseudo as possible without making them look out of context so they are not compilable. Also mock and prototype are considered the same, I just used them in conjunction to put things into context.

jgwigjjp

jgwigjjp2#

这取决于您的偏好。如果您正在构建独立的Composable或库,则首选使用Composable内部的状态。使用rememberXState()看到的任何类都保持状态变量。例如scrollState()

@Composable
fun rememberScrollState(initial: Int = 0): ScrollState {
    return rememberSaveable(saver = ScrollState.Saver) {
        ScrollState(initial = initial)
    }
} 

@Stable
class ScrollState(initial: Int) : ScrollableState {

/**
 * current scroll position value in pixels
 */
var value: Int by mutableStateOf(initial, structuralEqualityPolicy())
    private set

//代码的其余部分}
这是Jetpack Compose中常用的方法。我在构建的库中使用了这种方法,例如在这个image crop library中,我保留状态和Animatable。Animatable是低级默认动画类,也有自己的状态。

@Suppress("NotCloseable")
class Animatable<T, V : AnimationVector>(
    initialValue: T,
    val typeConverter: TwoWayConverter<T, V>,
    private val visibilityThreshold: T? = null
) {

internal val internalState = AnimationState(
    typeConverter = typeConverter,
    initialValue = initialValue
)

/**
 * Current value of the animation.
 */
val value: T
    get() = internalState.value

/**
 * Velocity vector of the animation (in the form of [AnimationVector].
 */
val velocityVector: V
    get() = internalState.velocityVector

/**
 * Returns the velocity, converted from [velocityVector].
 */
val velocity: T
    get() = typeConverter.convertFromVector(velocityVector)

/**
 * Indicates whether the animation is running.
 */
var isRunning: Boolean by mutableStateOf(false)
    private set

/**
 * The target of the current animation. If the animation finishes un-interrupted, it will
 * reach this target value.
 */
var targetValue: T by mutableStateOf(initialValue)
    private set

}

这种方法适用于不涉及业务逻辑而只涉及UI逻辑的UI组件。
当您需要根据业务逻辑(如搜索或从API获取结果)更新UI时,您应该使用Presenter类,它也可以是ViewModel。
最后但也是最不重要的一点是,人们现在质疑是否应该有一个带有Jetpack Compose的ViewModel,因为我们可以在AAC ViewModel中使用状态。
这个关于state holders的链接也是很好的阅读来源

相关问题