(Kotlin) Связь между фрагментами в MVVM

Я пытаюсь понять концепции MVVM, но мне трудно понять, как в этом случае общаться между классом модели и пользовательским интерфейсом (фрагментом).

Вот (дерьмовый, имейте в виду) код:

LoginFragment.kt

class LoginFragment: Fragment(), AuthListener {

override fun onCreateView(
    inflater: LayoutInflater,
    container: ViewGroup?,
    savedInstanceState: Bundle?
): View? {
    val binding = DataBindingUtil.inflate<CredentialsLoginFragmentBinding>(
        inflater,
        R.layout.credentials_login_fragment,
        container,
        false
    )
    val viewModel = ViewModelProviders.of(this).get(LoginViewModel::class.java)
    val view: View = binding.root
    val registerButton: Button = view.findViewById(R.id.register_button)
    binding.viewModel = viewModel
    viewModel.authListener = this

    registerButton.setOnClickListener {
        val transaction: FragmentTransaction? = fragmentManager?.beginTransaction()
        transaction?.replace(R.id.fragment_container, SignupFragment())?.commit()
    }
    return view
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    val constraintRoot: MotionLayout = view.findViewById(R.id.sign_in_root)
    ActivityUtils().switchLayoutAnimationKeyboard(constraintRoot = constraintRoot)

}

override fun onStarted() {
    Toast.makeText(context, "Started", Toast.LENGTH_SHORT).show()
}

override fun onSuccess() {
    Toast.makeText(context, "Success", Toast.LENGTH_SHORT).show()
}

override fun onError(message: String) {
    Toast.makeText(context, message, Toast.LENGTH_SHORT).show()
}}

LoginViewModel.kt

class LoginViewModel: ViewModel(){
var username: String? = null
var password: String? = null
var isCredentialsValid: Boolean = false
var authListener: AuthListener? = null
private val context: Context? = null

fun onLoginButtonClicked(view: View){

    if(username.isNullOrEmpty() || password.isNullOrEmpty()){
        authListener?.onError("Invalid username or password")
        isCredentialsValid = false
        return
    }

    if(!username.isNullOrEmpty() && password!!.length >= 8){
        isCredentialsValid = true
        authListener?.onSuccess()
    }else{
        authListener?.onError("Invalid")
    }
}}

Теперь предположим, что я ввожу имя пользователя и пароль, и оба они соответствуют критериям. Теперь я хотел бы, чтобы, когда я нажимаю кнопку «Войти», текущий фрагмент заменялся, например, фрагментом меню.

Как я мог добиться чего-то подобного? Я пытался заменить класс ViewModel, но это не сработало. Должен ли я взять результат «isCredentialsValid» из класса VM и соответствующим образом ответить в классе LoginFragment?

Спасибо.


person BenAndJerrys    schedule 01.04.2020    source источник


Ответы (2)


Вы должны использовать оперативные данные для обновления данных из viewModel для просмотра. Я опубликую код таким, каким он должен быть, но убедитесь, что вы должны понимать концепцию LiveData.

LoginViewModel.kt

class LoginViewModel: ViewModel(){
var username: String? = null
var password: String? = null
var isCredentialsValid: Boolean = false
var authListener: AuthListener? = null
private val context: Context? = null
// LiveData to udpate the UI
private val _isValidCredential = MutableLiveData<Boolean>()
val isValidCredential: LiveData<Boolean> = _isValidCredential


fun onLoginButtonClicked(view: View){

if(username.isNullOrEmpty() || password.isNullOrEmpty()){
    authListener?.onError("Invalid username or password")
    isCredentialsValid = false
    return
}

if(!username.isNullOrEmpty() && password!!.length >= 8){
    isCredentialsValid = true
// to update the value of live data wherever you need
    _isValidCredential.value = true
    authListener?.onSuccess()
}else{
    authListener?.onError("Invalid")
// to update the value of live data wherever you need
    _isValidCredential.value = false
}
}
}

Ваш фрагмент должен быть

LoginFragment.kt

class LoginFragment: Fragment(), AuthListener {

override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val binding = DataBindingUtil.inflate<CredentialsLoginFragmentBinding>(
    inflater,
    R.layout.credentials_login_fragment,
    container,
    false
)
val viewModel = ViewModelProviders.of(this).get(LoginViewModel::class.java)
val view: View = binding.root
val registerButton: Button = view.findViewById(R.id.register_button)
binding.viewModel = viewModel
viewModel.authListener = this

// This is the way you need to observe the value
viewModel.isValidCredential.observe(viewLifecycleOwner, Observer {

if(it){
// do your navigation stuff here
}else{
// do your stuff if not valid credential
}

    })

registerButton.setOnClickListener {
    val transaction: FragmentTransaction? = 
fragmentManager?.beginTransaction()
    transaction?.replace(R.id.fragment_container, SignupFragment())?.commit()
}
return view
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val constraintRoot: MotionLayout = view.findViewById(R.id.sign_in_root)
ActivityUtils().switchLayoutAnimationKeyboard(constraintRoot = constraintRoot)

}

override fun onStarted() {
Toast.makeText(context, "Started", Toast.LENGTH_SHORT).show()
}

override fun onSuccess() {
Toast.makeText(context, "Success", Toast.LENGTH_SHORT).show()
}

override fun onError(message: String) {
Toast.makeText(context, message, Toast.LENGTH_SHORT).show()
}}
person Raghavan R    schedule 01.04.2020
comment
@BenAndJerrys, голосующий за ответ, может быть полезен другим для ссылки - person Raghavan R; 02.04.2020
comment
Я слишком новичок для этого, я считаю - person BenAndJerrys; 03.04.2020

Типичный способ обратной связи с пользовательским интерфейсом из модели представления — использование livedata. . В вашей LoginViewModel вы должны установить для своих живых данных значение true или false. Внутри вашего представления LoginFragment.kt у вас будет наблюдатель. Эта работа наблюдателя заключается в том, чтобы срабатывать каждый раз, когда значение livedata изменилось. Таким образом, вы можете иметь логику в своем представлении, которая может либо отображать сообщение об ошибке liveData = false, либо запускать фрагмент меню = true.

Вот хороший пример использования livedata для передачи данных в представление (фрагмент) в документации: https://developer.android.com/topic/libraries/architecture/viewmodel#implement

person Kenny Sexton    schedule 01.04.2020