Skip to content

Instantly share code, notes, and snippets.

@Tadas44
Created July 20, 2019 08:55
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Tadas44/d5f10ac4c1fcf64ceed83148f3cc29fc to your computer and use it in GitHub Desktop.
Save Tadas44/d5f10ac4c1fcf64ceed83148f3cc29fc to your computer and use it in GitHub Desktop.
RxJava MVVM example
open class BaseFragment : Fragment() {
private var disposable = CompositeDisposable()
fun Disposable.addToDisposable() {
disposable.add(this)
}
override fun onDestroyView() {
super.onDestroyView()
disposable.clear()
}
fun getMainActivity() = activity as MainActivity
fun Observable<StateTracker.State>.showProgress(): Observable<StateTracker.State> {
val progress = ProgressDialog(context)
progress.setTitle(R.string.progress_title)
progress.setMessage(getString(R.string.progress_message))
progress.setCancelable(false) // disable dismiss by tapping outside of the dialog
progress.dismiss()
return observeOn(RxSchedulers.uiScheduler)
.doOnNext {
when (it) {
StateTracker.State.Loading -> progress.show()
else -> progress.dismiss()
}
}
}
fun Observable<StateTracker.State>.showError(): Observable<StateTracker.State> {
return observeOn(RxSchedulers.uiScheduler)
.doOnNext {
when (it) {
is StateTracker.State.Error -> showError(it.error)
}
}
}
private fun showError(error: Throwable) {
val message = (error as? ApiError)?.message ?: getString(R.string.error_title)
val dialog = MaterialAlertDialogBuilder(context)
.setMessage(message)
.show()
}
fun <T> Observable<T>.showApiError(apiErrorBlock: (String) -> Unit) =
showApiError(apiErrorBlock, {})
fun <T> Observable<T>.showApiError(apiErrorBlock: (String) -> Unit, errorBlock: () -> Unit = {}): Observable<T> {
return doOnNext { state ->
when (state) {
is StateTracker.State.Error -> {
when (state.error) {
is ApiError -> {
state.error.message?.run {
apiErrorBlock.invoke(this)
}
}
else -> {
errorBlock.invoke()
}
}
}
}
}
}
}
class RegisterFragment : BaseFragment() {
private val viewModel: RegisterViewModel by viewModel()
private val smsRetriever by lazy {
SmsRetriever.getClient(activity!!)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_register, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel.navigator = DefaultRegisterNavigator(findNavController())
bindViewModel()
}
private fun bindViewModel() {
val register = register_continue_button.clicks()
val textInput = register_text_input.textInputEditText.textChanges()
.skipInitialValue()
.doOnNext { register_text_input.textInputLayout.error = null }
val input = RegisterViewModel.Input(textInput, register)
val output = viewModel.transform(input)
output.register
.observeOn(RxSchedulers.uiScheduler)
.subscribe { smsRetriever.startSmsRetriever() }
.addToDisposable()
output.enabled
.subscribe { enabled ->
register_continue_button.isEnabled = enabled
}.addToDisposable()
output.state
.showProgress()
.showApiError {
showValidationDialog(it)
showInputMessage(it)
}
.subscribe()
.addToDisposable()
}
fun showValidationDialog(message: String) {
val dialog = MaterialAlertDialogBuilder(context)
.setTitle(R.string.register_error_alert_title)
.setMessage(message)
.setPositiveButton(R.string.button_ok) { dialog, _ ->
dialog.dismiss()
}
.show()
}
private fun showInputMessage(message: String) {
//using hardcoded error
register_text_input.textInputLayout.error = getString(R.string.register_error_text)
}
}
class RegisterViewModel(val registerUseCase: RegisterUseCase) : ViewModel() {
lateinit var navigator: RegisterNavigator
data class Input(
val number: Observable<CharSequence>,
val register: Observable<Unit>
)
data class Output(
val state: Observable<StateTracker.State>,
val enabled: Observable<Boolean>,
val register: Observable<Unit>
)
fun transform(input: Input): Output {
val stateTracker = StateTracker()
val enabled = input.number
.map { !it.isNullOrEmpty() }
val register = input.register
.withLatestFrom(input.number)
.map { it.second.toString() }
.flatMap { registerUseCase.register(it).toObservable().track(stateTracker) }
.withLatestFrom(input.number)
.map { it.second.toString() }
.doOnNext { navigator.toConfirmCode(it) }
.mapToUnit()
val state = stateTracker.asObservable()
return Output(state, enabled, register)
}
}
@Tadas44
Copy link
Author

Tadas44 commented Jul 20, 2019

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment