How to use flow binding - android

I try to handle clicks on my buttons and send action to viewModel
private fun subscribeUI() {
lifecycleScope.launch {
binding.loginButton
.clicks()
.onEach { }
.map { Action.WelcomeAction.SelectLogin }
.collect { viewModel.actions.offer(it) }
binding.homeButton
.clicks()
.onEach { }
.map { Action.WelcomeAction.SelectHome }
.collect { viewModel.actions.offer(it) }
binding.registerButton
.clicks()
.onEach {}
.map { Action.WelcomeAction.SelectRegister }
.collect { viewModel.actions.offer(it) }
}
}
Only action from login button comes to my view model. How can I merge these three flows into one? Probably that's the problem there are 3 action streams to view model

private fun subscribeUI() {
merge(
binding.loginButton.clicks().map { Action.WelcomeAction.SelectLogin },
binding.homeButton.clicks().map { Action.WelcomeAction.SelectHome },
binding.registerButton.clicks().map { Action.WelcomeAction.SelectRegister }
)
.onEach { viewModel.actions.offer(it) }
.launchIn(lifecycleScope)
}

Related

How to update RecyclerView when use retrofit?

i want to update RecyclerView when item removed but i dont' have idea to solve, i try to use notifydatasetchanged() but it don't work.
API Interface
interface RetrofitService {
#GET("/")
suspend fun getAllItem() : Response<List<Item>>
#POST("/delete")
suspend fun deleteItem(#Body item :Item) : Response<Item>
class Repository
class ItemRepository(private val retrofitService: RetrofitService) {
suspend fun getAllItem() = retrofitService.getAllItem()
suspend fun deleteItem(item : Item) = retrofitService.deleteItem(item)
}
in ViewModel, i handle result by two function(handleItemResponse and handleListItemResponse)
class ItemViewModel(private val itemRepository: ItemRepository) : ViewModel() {
val itemResponse: MutableLiveData<Resource<Item>> = MutableLiveData()
val listItemResponse : MutableLiveData<Resource<List<Item>>> = MutableLiveData()
fun getItemResponse() = viewModelScope.launch {
val response : Response<List<Item>> = itemRepository.getAllItem()
withContext(Dispatchers.Main) {
listItemResponse.postValue(handleListItemResponse(response))
}
}
fun deleteItem(item: Item) = viewModelScope.launch {
val response = itemRepository.deleteItem(item)
itemResponse.postValue(handleItemResponse(response))
}
private fun handleItemResponse(response: Response<Item>): Resource<Item> {
if (response.isSuccessful) {
response.body()?.let { resultResponse ->
return Resource.Success(resultResponse)
}
}
return Resource.Error(response.message())
}
private fun handleListItemResponse(response: Response<List<Item>>): Resource<List<Item>> {
if (response.isSuccessful) {
response.body()?.let { resultResponse ->
return Resource.Success(resultResponse)
}
}
return Resource.Error(response.message())
}
}
and class Resource to logging result
sealed class Resource<T>(
val data:T?=null,
val messaage : String? = null
) {
class Success<T>(data:T) : Resource<T>(data)
class Error<T>(messaage: String,data:T? = null) : Resource<T>(data,messaage)
class Loading<T> : Resource<T>()
}
in Fragment , i use ViewModel like this
viewModel.listItemResponse.observe(viewLifecycleOwner, Observer {
when (it) {
is Resource.Success -> {
Toast.makeText(context,"All Success",Toast.LENGTH_SHORT).show()
it.data?.let { itemReponse ->
listItem = itemReponse
adapter.setNotes(listItem)
}
}
is Resource.Error -> {
it.messaage?.let { msg ->
Log.e("AAA","ERR:$msg ")
}
}
}
})
viewModel.getItemResponse()
}
in Adapter , i getList by function setNotes
fun setNotes(items:List<Item>){
this.items = items
notifyDataSetChanged()
}
and function to delete item
private val onItemDelete:(Item)->Unit ={ item ->
viewModel.itemResponse.observe(viewLifecycleOwner, Observer {
when (it) {
is Resource.Success -> {
it.data?.let {
Toast.makeText(context,"Delete successfully!",Toast.LENGTH_SHORT).show()
}
}
is Resource.Error -> {
it.messaage?.let { msg ->
Toast.makeText(context," \"ERR:$msg \"",Toast.LENGTH_SHORT).show()
}
}
}
})
viewModel.deleteItem(item)
}
My english not well , so i hope you sympathize and help me, have a nice day,everyone!
I fixed it, i put it for anyone face same my problem.
private val onItemDelete:(Int)->Unit ={ pos ->
viewModel.itemResponse.observe(viewLifecycleOwner, Observer {
when (it) {
is Resource.Success -> {
it.data?.let {
listItem.removeAt(pos)
adapter.notifyItemRemoved(pos)
}
}
is Resource.Error -> {
it.messaage?.let { msg ->
Toast.makeText(context," \"ERR:$msg \"",Toast.LENGTH_SHORT).show()
}
}
}
})
viewModel.deleteItem(listItem[pos])
}
just callback position of item then get it in fragment.

Calling multiple viewmodel methods from launchWhenStarted does not work [duplicate]

This question already has answers here:
Collect from several stateflows
(4 answers)
Closed 1 year ago.
This code invokes two methods on the same viewmodel and listens for updates. But only the first method completes, the second does not event trigger.
private fun initData() {
lifecycleScope.launchWhenStarted {
viewModel.incrementCount().collect {
info { "Count: $it" }
}
viewModel.getAllTeams().collect {
when (it) {
is State.Success -> {
info { "Got teams with size: ${it.result}.size" }
}
is State.Error -> {
info { "Error getting teams: ${it.message}" }
}
State.Loading -> {
info { "Loading all teams" }
}
}
}
}
}
ViewModel
class DashboardViewModel : ViewModel(), com.droid.common.Logger {
fun incrementCount(): MutableStateFlow<Int> {
val countState = MutableStateFlow(0)
viewModelScope.launch {
repeat(5) {
countState.value = (it)
delay(1000)
}
}
return countState
}
fun getAllTeams(): MutableStateFlow<State> {
val state = MutableStateFlow<State>(State.None)
state.value = State.Loading
viewModelScope.launch {
try {
val allTeams = FootballAPIClient.apiService.getAllTeams()
state.value = State.Success(allTeams)
} catch (exception: Exception) {
error { "Error getting all teams: ${exception.message}" }
state.value = State.Error(exception.message.toString())
}
}
return state
}
However, calling them with separate lifecycleScope works
private fun initData() {
lifecycleScope.launchWhenStarted {
viewModel.incrementCount().collect {
info { "Count: $it" }
}
}
lifecycleScope.launchWhenStarted {
viewModel.getAllTeams().collect {
when (it) {
is State.Success -> {
info { "Got teams with size: ${it.result}.size" }
}
is State.Error -> {
info { "Error getting teams: ${it.message}" }
}
State.Loading -> {
info { "Loading all teams" }
}
}
}
}
}
I can't seem to understand this behavior, anybody knows why?
You will need different coroutines, since collect() is a suspending function that suspends until your Flow terminates.
The problem with launchWhenStarted is that while your newly emitted items will not be processed your producer will still run in the background.
For collecting multiple flows the currently recommended way is:
lifecycleScope.launch {
lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
launch {
viewModel.incrementCount().collect { ... }
}
launch {
viewModel.getAllTeams().collect { ... }
}
}
}

RxJava 3 Mapper

I am using RxJava3 in my project and I can't write a request, I've been wrestling my head for several hours.
I have List<Stream> and have a function that returns a Single<List<Topic>> by Stream, i want to get a Single<Map<Stream, List<Topic>>>,
fun getMockTopics(streamId: Long): Single<List<Topic>> {
return Single.just(listOf(Topic(1, "")))
}
typealias SteamTopics = Map<Stream, List<Topic>>
override fun getTopics(streams: List<Stream?>): Single<SteamTopics> {
return Observable.fromCallable { streams.filterNotNull() }.flatMapIterable { it }
.map { stream ->
Pair(stream, getMockTopics(streamId = stream.streamId))
}.flatMap {
TODO("???")
}
.toMap({ it.first }, { it.second })
}
fun getMockTopics(streamId: Long): Observable<List<Topic>> {
return Observable.just(listOf(Topic(1, "")))
}
override fun getTopics(streams: List<Stream?>): Single<SteamTopics> {
return Observable.fromCallable { streams.filterNotNull() }.flatMapIterable { it }
.flatMap({ stream ->
getMockTopics(streamId = stream.streamId)
}, { stream, topics ->
Pair(stream, topics)
}).toMap({ it.first }, { it.second })
}

How to wait until all viewmodel asynchronous operations are done

I need to execute something in my view after all of the asynchronous fetches has been done , for this I have a home framgnet that fetches 3 different sections in my backend
private fun populateSectionA(){
viewModel.fetchSectionA.observe(viewLifecycleOwner, Observer {
when(it){
is Resource.Loading -> { //Handling loading }
is Resource.Success -> {
//Handles data
}
is Resource.Failure -> { //Error handling }
}
})
}
private fun populateSectionB(){
viewModel.fetchSectionB.observe(viewLifecycleOwner, Observer {
when(it){
is Resource.Loading -> { //Handling loading }
is Resource.Success -> {
//Handles data
}
is Resource.Failure -> { //Error handling }
}
})
}
private fun populateSectionC(){
viewModel.fetchSectionC.observe(viewLifecycleOwner, Observer {
when(it){
is Resource.Loading -> { //Handling loading }
is Resource.Success -> {
//Handles data
}
is Resource.Failure -> { //Error handling }
}
})
}
Here are my observers in my view, what I need to know is when all of these 3 has finished to load my UI
From my viewmodel the fetch is the same for the three sections
val fetchSectionA = liveData(viewModelScope.coroutineContext + Dispatchers.IO) {
emit(Resource.Loading())
try {
emit(repo.fetchSectionA(sectionId))
} catch (e: Exception) {
emit(Resource.Failure(e))
}
}
val fetchSectionB = liveData(viewModelScope.coroutineContext + Dispatchers.IO) {
emit(Resource.Loading())
try {
emit(repo.fetchSectionB(sectionId))
} catch (e: Exception) {
emit(Resource.Failure(e))
}
}
val fetchSectionC = liveData(viewModelScope.coroutineContext + Dispatchers.IO) {
emit(Resource.Loading())
try {
emit(repo.fetchSectionC(sectionId))
} catch (e: Exception) {
emit(Resource.Failure(e))
}
}
Now, how can I know when all of these individual fetches has been done ? I dont want to use a counter in my UI that counts to certain number untill its reached, instead I would love to give a callback to my UI when all these operations finishes
If you have the following code:
fun <T1, T2, T3> combineTuple(f1: LiveData<T1>, f2: LiveData<T2>, f3: LiveData<T3>): LiveData<Triple<T1?, T2?, T3?>> = MediatorLiveData<Triple<T1?, T2?, T3?>>().also { mediator ->
mediator.value = Triple(f1.value, f2.value, f3.value)
mediator.addSource(f1) { t1: T1? ->
val (_, t2, t3) = mediator.value!!
mediator.value = Triple(t1, t2, t3)
}
mediator.addSource(f2) { t2: T2? ->
val (t1, _, t3) = mediator.value!!
mediator.value = Triple(t1, t2, t3)
}
mediator.addSource(f3) { t3: T3? ->
val (t1, t2, _) = mediator.value!!
mediator.value = Triple(t1, t2, t3)
}
}
Then you can do:
combineTuple(fetchSectionA, fetchSectionB, fetchSectionC)
.map { (sectionA, sectionB, sectionC) ->
val sectionA = sectionA.takeIf { it != Resource.Loading } ?: return#map null
val sectionB = sectionB.takeIf { it != Resource.Loading } ?: return#map null
val sectionC = sectionC.takeIf { it != Resource.Loading } ?: return#map null
return Triple(sectionA, sectionB, sectionC)
}
If you need more or less arity combiners for LiveData, check https://github.com/Zhuinden/livedata-combinetuple-kt

Android ViewState using RxJava or kotlin coroutines

I'm trying to learn how to use RxJava in Android, but have run into a dead end. I have the following DataSource:
object DataSource {
enum class FetchStyle {
FETCH_SUCCESS,
FETCH_EMPTY,
FETCH_ERROR
}
var relay: BehaviorRelay<FetchStyle> = BehaviorRelay.createDefault(FetchStyle.FETCH_ERROR)
fun fetchData(): Observable<DataModel> {
return relay
.map { f -> loadData(f) }
}
private fun loadData(f: FetchStyle): DataModel {
Thread.sleep(5000)
return when (f) {
FetchStyle.FETCH_SUCCESS -> DataModel("Data Loaded")
FetchStyle.FETCH_EMPTY -> DataModel(null)
FetchStyle.FETCH_ERROR -> throw IllegalStateException("Error Fetching")
}
}
}
I want to trigger an update downstream, whenever I change the value of relay, but this doesn't happen. It works when the Activity is initialized, but not when I'm updating the value. Here's my ViewModel, from where I update the value:
class MainViewModel : ViewModel() {
val fetcher: Observable<UiStateModel> = DataSource.fetchData().replay(1).autoConnect()
.map { result -> UiStateModel.from(result) }
.onErrorReturn { exception -> UiStateModel.Error(exception) }
.startWith(UiStateModel.Loading())
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.io())
fun loadSuccess() {
DataSource.relay.accept(DataSource.FetchStyle.FETCH_SUCCESS)
}
fun loadEmpty() {
DataSource.relay.accept(DataSource.FetchStyle.FETCH_EMPTY)
}
fun loadError() {
DataSource.relay.accept(DataSource.FetchStyle.FETCH_ERROR)
}
}
This is the code from the Activity that does the subsciption:
model.fetcher
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
uiState -> mainPresenter.loadView(uiState)
})
Ended up using kotlin coroutines instead, as I was unable to re-subscribe to ConnectableObservable and start a new fetch.
Here's the code for anyone interested.
The presenter:
class MainPresenter(val view: MainView) {
private lateinit var subscription: SubscriptionReceiveChannel<UiStateModel>
fun loadSuccess(model: MainViewModel) {
model.loadStyle(DataSource.FetchStyle.FETCH_SUCCESS)
}
fun loadError(model: MainViewModel) {
model.loadStyle(DataSource.FetchStyle.FETCH_ERROR)
}
fun loadEmpty(model: MainViewModel) {
model.loadStyle(DataSource.FetchStyle.FETCH_EMPTY)
}
suspend fun subscribe(model: MainViewModel) {
subscription = model.connect()
subscription.subscribe { loadView(it) }
}
private fun loadView(uiState: UiStateModel) {
when(uiState) {
is Loading -> view.isLoading()
is Error -> view.isError(uiState.exception.localizedMessage)
is Success -> when {
uiState.result != null -> view.isSuccess(uiState.result)
else -> view.isEmpty()
}
}
}
fun unSubscribe() {
subscription.close()
}
}
inline suspend fun <E> SubscriptionReceiveChannel<E>.subscribe(action: (E) -> Unit) = consumeEach { action(it) }
The view:
...
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
launch(UI) {
mainPresenter.subscribe(model)
}
btn_load_success.setOnClickListener {
mainPresenter.loadSuccess(model)
}
btn_load_error.setOnClickListener {
mainPresenter.loadError(model)
}
btn_load_empty.setOnClickListener {
mainPresenter.loadEmpty(model)
}
}
override fun onDestroy() {
super.onDestroy()
Log.d("View", "onDestroy()")
mainPresenter.unSubscribe()
}
...
The model:
class MainViewModel : ViewModel() {
val TAG = this.javaClass.simpleName
private val stateChangeChannel = ConflatedBroadcastChannel<UiStateModel>()
init {
/** When the model is initialized we immediately start fetching data */
fetchData()
}
override fun onCleared() {
super.onCleared()
Log.d(TAG, "onCleared() called")
stateChangeChannel.close()
}
fun connect(): SubscriptionReceiveChannel<UiStateModel> {
return stateChangeChannel.openSubscription()
}
fun fetchData() = async {
stateChangeChannel.send(UiStateModel.Loading())
try {
val state = DataSource.loadData().await()
stateChangeChannel.send(UiStateModel.from(state))
} catch (e: Exception) {
Log.e("MainModel", "Exception happened when sending new state to channel: ${e.cause}")
}
}
internal fun loadStyle(style: DataSource.FetchStyle) {
DataSource.style = style
fetchData()
}
}
And here's a link to the project on github.

Categories

Resources