Last active
February 8, 2020 11:31
-
-
Save liamkernighan/a63b2896e0601abe843ad7768041e33a to your computer and use it in GitHub Desktop.
LiveData MVI example
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.nasladdin.partner.activities.login | |
import android.os.Bundle | |
import android.view.KeyEvent | |
import androidx.appcompat.app.AppCompatActivity | |
import androidx.lifecycle.Observer | |
import androidx.lifecycle.ViewModelProvider | |
import com.nasladdin.partner.R | |
import com.nasladdin.partner.helpers.SnackbarFacade | |
/** | |
* Переадресовываем сюда при на всех Activity и фрагментах, защищённых аутентификацией. | |
*/ | |
class LoginActivity : AppCompatActivity() { | |
private lateinit var viewModel: LoginViewModel | |
private lateinit var snackbarFacade: SnackbarFacade | |
private lateinit var store: LoginStore | |
private lateinit var view: LoginView | |
override fun onCreate(savedInstanceState: Bundle?) { | |
super.onCreate(savedInstanceState) | |
setContentView(R.layout.login_activity) | |
initViewModel() | |
view = LoginView(this) | |
snackbarFacade = SnackbarFacade(this, findViewById(R.id.login_activity_intro_text)) | |
store = LoginStore(viewModel, lifecycle) | |
setListeners() | |
} | |
private fun initViewModel() { | |
viewModel = ViewModelProvider(this).get(LoginViewModel::class.java) | |
viewModel.state.observe(this, Observer { render(it) }) | |
viewModel.viewEffect.observe(this, Observer { handleEffect(it) }) | |
} | |
private fun render(viewState: LoginViewState) = when (viewState) { | |
is LoginViewState.AwaitingCredentials -> { | |
view.setLoginButtonEnabled(true) | |
view.setLoaderEnabled(false) | |
} | |
is LoginViewState.IsLoading -> { | |
view.setLoginButtonEnabled(false) | |
view.setLoaderEnabled(true) | |
} | |
is LoginViewState.LoggedInSuccessfully -> { | |
finish() | |
} | |
} | |
private fun handleEffect(viewEffect: LoginViewEffect) = snackbarFacade.warning(viewEffect.message) | |
private fun setListeners() { | |
view.button.setOnClickListener { | |
store.onLoginButtonClick(view.loginText, view.passwordText) | |
} | |
// todo в хелпер | |
view.passwordEditField.setOnKeyListener { v, keyCode, event -> | |
if (event.action == KeyEvent.ACTION_DOWN && keyCode == KeyEvent.KEYCODE_ENTER) { | |
store.onLoginButtonClick(view.loginText, view.passwordText) | |
true | |
} | |
false | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.nasladdin.partner.activities.login | |
import androidx.lifecycle.Lifecycle | |
import com.nasladdin.partner.application.GlobalApplication | |
import com.nasladdin.partner.boilerplate.StoreBase | |
import com.nasladdin.partner.helpers.sharedpreferences.LoginManager | |
import com.nasladdin.partner.repos.remote.fuel.FuelAuthRepository | |
import com.nasladdin.partner.repos.remote.fuel.HttpError | |
import kotlinx.coroutines.* | |
class LoginStore(private val vm: LoginViewModel, lifecycle: Lifecycle): StoreBase(lifecycle) { | |
private val authRepository: FuelAuthRepository = GlobalApplication.dependencyContainer.fuelAuthRepository | |
private val loginManager: LoginManager = GlobalApplication.dependencyContainer.loginManager | |
fun onLoginButtonClick(login: String, password: String) = launch { | |
if (login.isBlank() || password.isBlank()) { | |
vm.viewEffect.value = LoginViewEffect("Заполните, пожалуйста, логин и пароль") | |
return@launch | |
} | |
vm.state.value = LoginViewState.IsLoading | |
withContext(Dispatchers.IO) { | |
authRepository.login(login, password) | |
} | |
.mapLeft { | |
vm.viewEffect.value = LoginViewEffect(it.errorHandler()) | |
vm.state.value = LoginViewState.AwaitingCredentials | |
} | |
.map { | |
loginManager.saveCredentialsToPreferences(it.accessToken, it.refreshToken) | |
vm.state.value = LoginViewState.LoggedInSuccessfully | |
} | |
} | |
private fun HttpError.errorHandler() = when (this) { | |
is HttpError.WrongCredentialsException -> "Логин или пароль неверные" | |
is HttpError.NetworkUnreachableException -> "Нет связи с сервером Насладдина. Возможно, отсутствует соединение с интернетом" | |
else -> "Неизвестная ошибка, пожалуйста, попробуйте позже $this" | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.nasladdin.partner.activities.login | |
import android.view.View | |
import android.widget.Button | |
import android.widget.EditText | |
import com.nasladdin.partner.R | |
class LoginView(private val activity: LoginActivity) { | |
val button: Button = activity.findViewById(R.id.login_login_button) | |
val passwordEditField: EditText = activity.findViewById(R.id.login_password_text) | |
fun setLoginButtonEnabled(enabled: Boolean) { | |
val button = activity.findViewById<Button>(R.id.login_login_button) | |
button.isEnabled = enabled | |
val colorCode = if (enabled) R.color.bootstrap_success else R.color.button_default | |
button.setBackgroundColor(activity.resources.getColor(colorCode)) | |
} | |
fun setLoaderEnabled(enabled: Boolean) { | |
val loader = activity.findViewById<View>(R.id.progress_bar) | |
loader.visibility = if (enabled) View.VISIBLE else View.GONE | |
} | |
val loginText: String | |
get() { | |
val loginText = activity.findViewById<EditText>(R.id.login_login_text) | |
return loginText.text.toString() | |
} | |
val passwordText: String | |
get() { | |
val passwordText = activity.findViewById<EditText>(R.id.login_password_text) | |
return passwordText.text.toString() | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.nasladdin.partner.activities.login | |
import androidx.lifecycle.MutableLiveData | |
import androidx.lifecycle.ViewModel | |
import androidx.lifecycle.ViewModelProvider | |
import com.nasladdin.partner.application.GlobalApplication | |
import com.nasladdin.partner.boilerplate.BaseViewModel | |
import com.nasladdin.partner.helpers.sharedpreferences.LoginManager | |
import com.nasladdin.partner.repos.remote.fuel.FuelAuthRepository | |
import com.nasladdin.partner.repos.remote.fuel.HttpError | |
import kotlinx.coroutines.Dispatchers | |
import kotlinx.coroutines.launch | |
import kotlinx.coroutines.withContext | |
class LoginViewModel : ViewModel() { | |
val state = MutableLiveData<LoginViewState>() | |
val viewEffect = MutableLiveData<LoginViewEffect>() | |
} | |
sealed class LoginViewState { | |
object AwaitingCredentials: LoginViewState() | |
object IsLoading: LoginViewState() | |
object LoggedInSuccessfully: LoginViewState() | |
} | |
data class LoginViewEffect(val message: String) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.nasladdin.partner.boilerplate | |
import androidx.lifecycle.Lifecycle | |
import androidx.lifecycle.LifecycleObserver | |
import androidx.lifecycle.OnLifecycleEvent | |
import kotlinx.coroutines.CoroutineScope | |
import kotlinx.coroutines.Dispatchers | |
import kotlinx.coroutines.Job | |
import kotlin.coroutines.CoroutineContext | |
abstract class StoreBase(private val lifecycle: Lifecycle): CoroutineScope, LifecycleObserver { | |
init { | |
@Suppress("LeakingThis") | |
lifecycle.addObserver(this) | |
} | |
public val compositeJob = Job() | |
override val coroutineContext: CoroutineContext | |
get() = Dispatchers.Main + compositeJob | |
@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY) | |
private fun onDestroy() { | |
compositeJob.cancel() | |
lifecycle.removeObserver(this) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment