Hello I tried to make Coroutines Flow (using callbackFlow) and tried to convert it to live data, but it seems it's not updating.
You can see my code below:
#ExperimentalCoroutinesApi
suspend fun checkInDanger(): Flow<NetworkStatus<List<UserFire>>> = callbackFlow {
val check = fs.collection(UserFire.COLLECTION).whereEqualTo(UserFire.DANGER, true)
.addSnapshotListener { value, e ->
if (e != null) {
trySend(NetworkStatus.Failed("Error occurred\n${e.code}"))
return#addSnapshotListener
}
if (value == null || value.isEmpty) trySend(NetworkStatus.Empty)
else {
val users = value.map { it.toObject(UserFire::class.java) }
trySend(NetworkStatus.Success(users))
}
}
awaitClose { }
}.flowOn(Dispatchers.IO)
On my repositories:
#ExperimentalCoroutinesApi
override suspend fun checkInDanger(): Flow<Status<List<User>>> = flow {
when (val result = network.checkInDanger().first()) {
is NetworkStatus.Success -> emit(Status.Success(result.data.map {
MapVal.userFireToDom(it)
}))
is NetworkStatus.Empty -> emit(Status.Success(listOf<User>()))
is NetworkStatus.Failed -> emit(Status.Error(null, result.error))
}
}
In my ViewModel:
val checkInDanger = liveData(Dispatchers.IO) {
try {
useCase.checkInDanger().collectLatest {
emit(it)
}
} catch (e: Exception) {
e.printStackTrace()
}
}
But when I changed the value in my Firebase, it's not fetching new data...
Anyone know why it's not fixed
I kind of find the way, but it's with callback, if we used callback, it can fetch data update even without live-data, but for my case I tried to push the callback to live data again,
so the code will be like this:
fun checkInDanger(networkStatus: (NetworkStatus<List<UserFire>>) -> Unit) {
fs.collection(UserFire.COLLECTION).whereEqualTo(UserFire.DANGER, true)
.addSnapshotListener { value, e ->
if (e != null) {
networkStatus(NetworkStatus.Failed("Error occurred\n${e.code}"))
return#addSnapshotListener
}
if (value == null || value.isEmpty) networkStatus(NetworkStatus.Empty)
else {
val users = value.map { it.toObject(UserFire::class.java) }
networkStatus(NetworkStatus.Success(users))
}
}
}
In my repositories:
override fun checkInDanger(callback: (Status<List<User>>) -> Unit) {
network.checkInDanger { result ->
when (result) {
is NetworkStatus.Success -> callback(Status.Success(result.data.map {
MapVal.userFireToDom(it)
}))
is NetworkStatus.Empty -> callback(Status.Success(listOf<User>()))
is NetworkStatus.Failed -> callback(Status.Error(null, result.error))
}
}
}
In my ViewModel:
fun checkInDanger(callback: (Status<List<User>>) -> Unit) = useCase.checkInDanger { callback(it) }
val setUsers = MutableLiveData<List<User>>().apply { this.value = listOf() }
val users: LiveData<List<User>> = setUsers
In my UI Class (Fragment Main):
val inDangerCallback: (Status<List<User>>) -> Unit = {
if (relative != null) {
when (it) {
is Status.Success ->
viewModel.setUsers.value =
it.data?.filter { user -> user.username in relative!!.pure }
else -> requireView().createSnackBar(it.error!!, 1000)
}
}
}
viewModel.checkInDanger(inDangerCallback)
viewModel.users.observe(viewLifecycleOwner) { users ->
println(users.size})
users?.forEach { user -> println(user.username) }
}
And the code can run perfectly and update automatically...
Related
the app have a chatting function so I used 'stackFromEnd' method of recyclerview to show a last item of list firstly like other chatting app however, it not worked. it stopped in the middle of placing message items.
MessageActivity OnCreate
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_message)
binding.lifecycleOwner = this
val intent = intent
room = intent.getSerializableExtra("room") as RoomEntity
roomUid = room!!.uid
other = intent.getSerializableExtra("other") as RoomMemberEntity
ownUid = AppContext.uid
layoutManager = LinearLayoutManager(this)
layoutManager.stackFromEnd = true
layoutManager.isSmoothScrollbarEnabled = true
binding.recyclerMessages.recycledViewPool.setMaxRecycledViews(0,0)
binding.recyclerMessages.layoutManager = layoutManager
adapter = MessageAdapter(messageList, ownUid, other)
binding.recyclerMessages.adapter = adapter
binding.buttonSend.setOnClickListener {
val text = binding.editMessage.text.toString()
if (text.length > 0) {
binding.editMessage.text!!.clear()
lifecycleScope.launch(Dispatchers.IO) {
val sendResponse = viewModel.sendMessage(text, roomUid)
when(sendResponse) {
is Response.Error -> {
withContext(Dispatchers.Main) {
Toast.makeText(this#MessageActivity,"message not sent due to the internet connection error.",Toast.LENGTH_SHORT)
}
}
else -> {
}
}
}
}
}
binding.editMessage.doAfterTextChanged { text ->
if (text!!.length > 0)
binding.buttonSend.visibility = View.VISIBLE
else
binding.buttonSend.visibility = View.GONE
}
}
MessageActivity OnStart
lifecycleScope.launch(Dispatchers.IO) {
viewModel.fetchMessage(roomUid).collect { fetchResponse->
when (fetchResponse) {
is Response.Success -> {
val map = fetchResponse.data
val type = map.keys.first()
val message = map.get(type)
if (message != null) {
if (messageList.contains(message)) {
val index = messageList.indexOf(message)
messageList.set(index, message)
} else {
messageList.add(message)
}
if (type == ADDED) {
if (message.read == false && !message.sender.equals(ownUid)) {
val readResponse = viewModel.readMessage(roomUid, message.uid)
when(readResponse) {
is Response.Error -> {
}
else -> {
}
}
}
}
withContext(Dispatchers.Main) {
adapter.changeMessages(messageList)
adapter.notifyDataSetChanged()
}
} else {
}
}
is Response.No -> {
}
is Response.Error -> {
}
else -> {
}
}
}
}
ViewModel
private fun _fetchMessage (roomUid : String) : Flow<Response<Map<Int, MessageEntity>>> {
val flow = repository.fetchMessage(roomUid).shareIn(viewModelScope, SharingStarted.WhileSubscribed(), 0)
return flow
}
fun fetchMessage (roomUid: String) : Flow<Response<Map<Int, MessageEntity>>> {
return _fetchMessage(roomUid)
}
DataSourceImpl
val reference =
databaseReference.child("messages").child(roomUid)
val subscription =
reference.addChildEventListener(object : ChildEventListener {
override fun onChildAdded(
snapshot: DataSnapshot,
previousChildName: String?
) {
val message = snapshot.getValue(MessageEntity::class.java)
if (message != null)
trySend(Response.Success(mapOf(ADDED to message )))
else
trySend(Response.Success(mapOf(RESPONSE_NULL to MessageEntity())))
}
override fun onChildChanged(
snapshot: DataSnapshot,
previousChildName: String?
) {
val message = snapshot.getValue(MessageEntity::class.java)
if (message != null)
trySend(Response.Success(mapOf(CHANGED to message)))
else
trySend(Response.Success(mapOf(RESPONSE_NULL to MessageEntity())))
}
override fun onChildRemoved(snapshot: DataSnapshot) {
}
override fun onChildMoved(
snapshot: DataSnapshot,
previousChildName: String?
) {
}
override fun onCancelled(error: DatabaseError) {
}
} )
awaitClose {
reference.removeEventListener(subscription)
channel.close()
}
why this error occurred? is it due to callbackFlow??
Issue (as you can see, the window does not show the end of messages. even usage of setStackFromEnd)
What I expected and wanted
Try this binding.rvChatMessage.layoutManager = LinearLayoutManager(this ,RecyclerView.VERTICAL,true) and remove stackfromend
I am learning clean architecture and Kotlin Flow. I want to check is user mail exists in the Firebase Auth base. However, when I threw an error to the flow function, app is crash.
CheckUserUseCase.kt
class CheckUserUseCase #Inject constructor(private val repository: SignInRepository) {
operator fun invoke(mail: String): Flow<Status<Boolean, String>> = flow {
emit(Status.Loading(data = null))
try {
repository.isUserExists(mail = mail)
emit(Status.Success(data = true))
} catch (e: Exception) {
emit(Status.Error(message = e.message, data = false))
}
}
}
SignInRepository.kt
interface SignInRepository {
suspend fun isUserExists(mail: String)
}
SignInRepositoryImpl.kt
class SignInRepositoryImpl #Inject constructor(private val firebaseUserActions: FirebaseUserActions) : SignInRepository {
override suspend fun isUserExists(mail: String) {
firebaseUserActions.isUserExists(mail = mail)
}
}
FirebaseAuthentication.kt
class FirebaseAuthentication #Inject constructor(private val auth: FirebaseAuth) : FirebaseUserActions {
override suspend fun isUserExists(mail: String){
auth.fetchSignInMethodsForEmail(mail).addOnCompleteListener { task ->
task.result.signInMethods?.let {
if (it.size != 0) Log.i("App.tag", "True.")
else throw IOException() <-- Crash point.
}
}.addOnFailureListener { e -> e.printStackTrace() }
.await()
}
}
How can I return a state to Kotlin Flow method? Thank you!
Please try the following approach:
override suspend fun isUserExists(mail: String): Status {
return try {
val result = auth.fetchSignInMethodsForEmail(mail).await()
result.signInMethods?.let {
if (it.isNotEmpty()) {
Status.Success(data = true)
} else {
Status.Error(message = "No data", data = false)
}
} ?: Status.Error(message = "No Data", data = false)
} catch (e: Exception) {
Status.Error(message = e.message, data = false)
}
}
In CheckUserUseCase class just emit the result of calling isUserExists():
emit(Status.Loading(data = null))
emit(repository.isUserExists(mail = mail))
Try
it.size != 0 && it.size != null
and
if (task.isSuccessful()) {
[...]
task.result.signInMethods?.let {
[...]
}
I have implemented MVVM architecture in one of my projects. In which I have created a ViewModel, Repository, and ViewModelFactory to make API call. I'm showing a list of products on the wish list. There is a facility where you can remove a particular product from the wish list. When my activity opens its API calls and renders the data but when I remove the product from the recycler view list need to make an API call which gets the remaining list of products available in the wishlist. When I change the configuration of the device it also makes API calls. How can I survive the configuration changes?
Below is the code from Activity
val repository = WishlistRepository(activity)
wishlistViewModel = ViewModelProvider(
this,
WishlistViewModelFactory(repository)
)[WishlistViewModel::class.java]
wishlistObservables()
if (preferenceUtils.getPrefBoolean(Constants.IS_LOGGED_IN)) {
if (wishlistViewModel.wishListLiveData.value == null) {
prepareParams()
}
} else {
val intent = Intent(activity, LoginActivity::class.java)
intent.putExtra("fromScreen", 2)
startActivity(intent)
}
prepareParams method
private fun prepareParams() {
if (Utils.isNetworkAvailable(activity)) {
val params = mutableMapOf<String, String>()
params["customer_id"] = preferenceUtils.getPreString(Constants.USER_ID)
params["store"] = preferenceUtils.getPreInt(Constants.STORE).toString()
wishlistViewModel.getWishList(params)
} else {
Utils.snackBar(binding.coordinatorWishlist, getString(R.string.network))
}
}
ViewModel Code
val wishListLiveData: LiveData<Resource<WishlistModel>> get() = repository.wishLiveData
fun getWishList(params: Map<String, String>) {
repository.getWishlistApiCall(params)
}
Repository Code
private val wishListLiveData = MutableLiveData<Resource<WishlistModel>>()
val wishLiveData:LiveData<Resource<WishlistModel>> get() = wishListLiveData
fun getWishlistApiCall(params:Map<String,String>){
if (Utils.isNetworkAvailable(activity)) {
wishListLiveData.postValue(Resource.Loading())
retroService.getWishListApiCall(params).enqueue(object : Callback<WishlistModel> {
override fun onResponse(call: Call<WishlistModel>, response: Response<WishlistModel>) {
try {
if (response.body() != null) {
if (response.isSuccessful) {
response.body()?.let {
if (it.code == 200) {
if (it.status == "1") {
wishListLiveData.postValue(Resource.Success(it))
} else {
wishListLiveData.postValue(Resource.Error(it.message))
}
} else if (it.code == 404) {
wishListLiveData.postValue(
Resource.Error(
activity.getString(
R.string.four_hundred_four_not_found
)
)
)
} else if (it.code == 500) {
wishListLiveData.postValue(
Resource.Error(
activity.getString(
R.string.five_hundred_server_error
)
)
)
} else {
wishListLiveData.postValue(Resource.Error(it.message))
}
}
} else {
wishListLiveData.postValue(Resource.Error(response.message()))
}
} else {
wishListLiveData.postValue(Resource.Error(response.message()))
}
} catch (e: Exception) {
wishListLiveData.postValue(Resource.Error(e.message.toString()))
}
}
override fun onFailure(call: Call<WishlistModel>, t: Throwable) {
try {
wishListLiveData.postValue(Resource.Error(t.message.toString()))
} catch (e: Exception) {
wishListLiveData.postValue(Resource.Error(e.message.toString()))
}
}
})
} else {
wishListLiveData.postValue(Resource.Error(activity.getString(R.string.network)))
}
}
ViewModelFactory Code
class WishlistViewModelFactory(private val repository: WishlistRepository) :
ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return WishlistViewModel(repository) as T
}
}
Remove wishlist observables
private fun removeObservables() {
removeWishlistViewModel.removeWishListLiveData.observe(this) {
when (it.status) {
Status.LOADING -> {
Utils.showDialog(activity)
}
Status.SUCCESS -> {
Utils.hideDialog()
if (it.data != null) {
it.data.let { removeWishModel ->
Utils.snackBar(
binding.coordinatorWishlist,
removeWishModel.message, Constants.SNACK_SUCCESS_COLOR
)
val activityFragmentMessageEvent =
Events.ActivityFragmentMessage("Wishlist")
EventBus.getDefault().post(activityFragmentMessageEvent)
prepareParams()
}
}
}
Status.ERROR -> {
Utils.hideDialog()
Utils.snackBar(binding.coordinatorWishlist, it.errorMessage.toString())
}
}
}
}
When I call prepareParams method again it's will make an API call of getting the wish-listed products. After that when I change the configuration from portrait to landscape it's starting again to make API calls again and again.
How can I resolve this issue? Please help me to resolve this issue with MVVM. I'm begginner in MVVM architecture.
Thank you
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.
Trying to stick to the MVVM pattern rules. The final data should come to the ViewModel (I'm making an exception for Glide, but that's not the point).
Created several subcollections in the document (likes, comments, images, and so on).
After creating them, I ran into the problem of requesting the Firestore inside the main request. The data just doesn't have time to arrive.
How do I wait for data before "data.add" in such a situation?
ViewModel
init {
data = loadData()
}
fun getData(loadNextData: Boolean): LiveData<Response<List<Model>>> {
if (loadNextData)
data = loadData()
return data
}
private fun loadData(): LiveData<Response<List<Model>>> {
return liveData(viewModelScope.coroutineContext + Dispatchers.IO) {
emit(Response.Loading)
val result = repository.loadData()
if (result is Response.Success || result is Response.Error)
emit(result)
}
}
Repository
suspend fun loadData(): Response<List<Model>> {
return suspendCoroutine { continuation ->
if (LAST_DOCUMENT == null) {
firebaseRepository.getReference(DEFAULT_COLLECTION_NAME, DEFAULT_ORDER_FIELD, DEFAULT_ORDER_DIRECTION)
} else {
firebaseRepository.getReference(DEFAULT_COLLECTION_NAME, DEFAULT_ORDER_FIELD, DEFAULT_ORDER_DIRECTION, LAST_DOCUMENT!!)
}
.get()
.addOnSuccessListener { query ->
try {
LAST_DOCUMENT = query.documents.lastOrNull()
query.documents.forEach { document ->
document.toObject(ModelDTO::class.java)?.let {
it.id = document.id
it.likes_count = // TODO
it.comments_count = // TODO
it.images_uri.add(//TODO)
data.add(it.mapToEntity())
}
}
continuation.resume(Response.Success(data))
} catch (exception: Exception) {
continuation.resume(Response.Error(exception))
}
}
.addOnFailureListener { exception ->
continuation.resume(Response.Error(exception))
}
}
}