As you can see in the picture, the documents in the person collection refer to the statistics collection.
I can pull this data like this
PersonRepository
override fun getPersonsFromFirestore(): Flow<Response<List<Person>>> = callbackFlow {
val snapshotListener =
personsCollection.addSnapshotListener { snapshot, e ->
val response = if (snapshot != null) {
val personList = snapshot.toObjects(Person::class.java)
Response.Success(personList)
} else {
throw Error(e?.message ?: e.toString())
}
trySend(response).isSuccess
}
awaitClose {
snapshotListener.remove()
}
}
Model
data class Person(
val id: Int=0,
val name: String="",
val surname: String="",
val image_url: String="",
val biography: String="",
val team: String="",
val statistics: DocumentReference? = null,
var personStatistics: PersonStatistics? = null
)
How can I convert Document Reference to object here?
I tried this first
override fun getPersonsFromFirestore(): Flow<Response<List<Person>>> = callbackFlow {
val snapshotListener =
personsCollection.addSnapshotListener { snapshot, e ->
val response = if (snapshot != null) {
val personList = mutableStateListOf<Person>()
snapshot.onEach {
val person = it.toObject(Person::class.java)
person.statistics!!.get().addOnSuccessListener {
val personStatistics = it.toObject(PersonStatistics::class.java)
person.personStatistics = personStatistics
}
personList.add(person)
}
//val personList = snapshot.toObjects(Person::class.java)
Response.Success(personList)
} else {
throw Error(e?.message ?: e.toString())
}
trySend(response).isSuccess
}
awaitClose {
snapshotListener.remove()
}
}
but in this approach Person Statistics comes first as null. It is added later. How can I bring them all at once? Or is there another better way?
Okey, This is how I solved it,
override fun getPersonsFromFirestore(): Flow<Response<List<Person>>> = callbackFlow {
val snapshotListener =
personsCollection.addSnapshotListener { snapshot, e ->
val response = if (snapshot != null) {
val personList = mutableStateListOf<Person>()
snapshot.onEach {
val person = it.toObject(Person::class.java)
val statistics = it.data["statistics"] as DocumentReference
statistics.addSnapshotListener { value, error ->
if (value!=null){
person.personStatistics = value.toObject(PersonStatistics::class.java)
personList.add(person)
}else{
throw Error(error?.message ?: error.toString())
}
}
}
Response.Success(personList)
} else {
throw Error(e?.message ?: e.toString())
}
trySend(response).isSuccess
}
awaitClose {
snapshotListener.remove()
}
}
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 am making a user registration with Authentication, but in turn, I insert these registrations in a collection of Firebase Realtime Database.
The structure of the database is as follows:
viewmodel of my register screen:
class RegisterViewModel : ViewModel() {
var email = mutableStateOf("")
var userName = mutableStateOf("")
var password = mutableStateOf("")
var status = mutableStateOf("1")
val state = mutableStateOf(RegisterState())
val user = FirebaseAuth.getInstance().currentUser
val loginPresenter = AuthPresenter()
fun registrar(email: String, password: String) {
viewModelScope.launch {
state.value = state.value.copy(isLoading = true)
if (
Common.isValidString(email.trim()) &&
Common.isValidPassword(password.trim()) &&
Common.isValidName(userName.value.trim()) &&
Common.isValidStatus(status.value)
) {
loginPresenter.signUp(email.trim(), password).addOnSuccessListener {
try {
val empleado = Empleado(
it.user!!.uid,
userName.value.trim(),
it.user!!.email!!,
status.value,
)
loginPresenter.createUserInDb(empleado).addOnSuccessListener {
state.value = state.value.copy(isSuccess = true)
}.addOnFailureListener { exception ->
state.value = state.value.copy(isError = exception.message)
}
} catch (e: Exception) {
state.value = state.value.copy(isError = e.message)
}
}.addOnFailureListener {
state.value = state.value.copy(isLoading = false)
state.value = state.value.copy(isError = it.message)
}
} else {
state.value = state.value.copy(isLoading = false)
state.value = state.value.copy(isError = "Rellena bien los campos")
}
}
}
fun dismiss() {
state.value = state.value.copy(isError = null)
}
And here the calls:
override suspend fun signUp(email: String, password: String): Task<AuthResult> {
return mAuth.createUserWithEmailAndPassword(email, password)
}
override fun createUserInDb(user: Empleado): Task<Void> {
return usersRef.child(user.uid!!).setValue(user.toMap())
}
First of all, the collection is: "employees"
and the document fields are:
.uid
.userName
.e-mail
.status
What I want to do is the following;
If there is no document in the "employees" collection;
The status field of the document to be inserted takes a value of 38. But if a document already exists in the collection, it takes the value of 1
I have set up Paging 3 with offline caching using a RemoteMediator. After process death, the RecyclerView immediately restores the correct scrolling position. However, since we need to send the search query again it triggers a LoadType.REFRESH which clears the current search results from the cache and replaces them with new values. This brings us back to the start of the list.
My RemoteMediator:
private const val NEWS_STARTING_PAGE_INDEX = 1
class SearchNewsRemoteMediator(
private val searchQuery: String,
private val newsDb: NewsArticleDatabase,
private val newsApi: NewsApi
) : RemoteMediator<Int, NewsArticle>() {
private val newsArticleDao = newsDb.newsArticleDao()
override suspend fun load(
loadType: LoadType,
state: PagingState<Int, NewsArticle>
): MediatorResult {
val page = when (loadType) {
LoadType.REFRESH -> {
val remoteKeys = getRemoteKeyClosestToCurrentPosition(state)
remoteKeys?.nextKey?.minus(1) ?: NEWS_STARTING_PAGE_INDEX
}
LoadType.PREPEND -> {
val remoteKeys = getRemoteKeyForFirstItem(state)
?: throw InvalidObjectException("Remote key should not be null for $loadType")
val prevKey = remoteKeys.prevKey
if (prevKey == null) {
return MediatorResult.Success(endOfPaginationReached = true)
}
remoteKeys.prevKey
}
LoadType.APPEND -> {
val remoteKeys = getRemoteKeyForLastItem(state)
if (remoteKeys == null || remoteKeys.nextKey == null) {
throw InvalidObjectException("Remote key should not be null for $loadType")
}
remoteKeys.nextKey
}
}
return try {
delay(2000)
val apiResponse = newsApi.searchNews(searchQuery, page, state.config.pageSize)
val serverSearchResults = apiResponse.articles
val endOfPaginationReached = serverSearchResults.isEmpty()
val bookmarkedArticles = newsArticleDao.getAllBookmarkedArticles().first()
val cachedBreakingNewsArticles = newsArticleDao.getCachedBreakingNews().first()
val searchResults = serverSearchResults.map { serverSearchResultArticle ->
val bookmarked = bookmarkedArticles.any { bookmarkedArticle ->
bookmarkedArticle.url == serverSearchResultArticle.url
}
val inBreakingNewsCache =
cachedBreakingNewsArticles.any { breakingNewsArticle ->
breakingNewsArticle.url == serverSearchResultArticle.url
}
NewsArticle(
title = serverSearchResultArticle.title,
url = serverSearchResultArticle.url,
urlToImage = serverSearchResultArticle.urlToImage,
isBreakingNews = inBreakingNewsCache,
isBookmarked = bookmarked,
isSearchResult = true
)
}
newsDb.withTransaction {
if (loadType == LoadType.REFRESH) {
newsDb.searchRemoteKeyDao().clearRemoteKeys()
newsArticleDao.resetSearchResults()
newsArticleDao.deleteAllObsoleteArticles()
}
val prevKey = if (page == NEWS_STARTING_PAGE_INDEX) null else page - 1
val nextKey = if (endOfPaginationReached) null else page + 1
val remoteKeys = serverSearchResults.map { article ->
SearchRemoteKeys(article.url, prevKey, nextKey)
}
newsDb.searchRemoteKeyDao().insertAll(remoteKeys)
newsDb.newsArticleDao().insertAll(searchResults)
}
MediatorResult.Success(endOfPaginationReached)
} catch (exception: IOException) {
MediatorResult.Error(exception)
} catch (exception: HttpException) {
MediatorResult.Error(exception)
}
}
private suspend fun getRemoteKeyForLastItem(state: PagingState<Int, NewsArticle>): SearchRemoteKeys? =
state.pages.lastOrNull { it.data.isNotEmpty() }?.data?.lastOrNull()
?.let { article ->
newsDb.searchRemoteKeyDao().getRemoteKeyFromArticleUrl(article.url)
}
private suspend fun getRemoteKeyForFirstItem(state: PagingState<Int, NewsArticle>): SearchRemoteKeys? =
state.pages.firstOrNull { it.data.isNotEmpty() }?.data?.firstOrNull()
?.let { article ->
newsDb.searchRemoteKeyDao().getRemoteKeyFromArticleUrl(article.url)
}
private suspend fun getRemoteKeyClosestToCurrentPosition(
state: PagingState<Int, NewsArticle>
): SearchRemoteKeys? =
state.anchorPosition?.let { position ->
state.closestItemToPosition(position)?.url?.let { articleUrl ->
newsDb.searchRemoteKeyDao().getRemoteKeyFromArticleUrl(articleUrl)
}
}
}
The repository method that instantiates it:
fun getSearchResults(query: String): Flow<PagingData<NewsArticle>> =
Pager(
config = PagingConfig(pageSize = 20, enablePlaceholders = false),
remoteMediator = SearchNewsRemoteMediator(query, newsArticleDatabase, newsApi),
pagingSourceFactory = { newsArticleDatabase.newsArticleDao().getSearchResultsPaged() }
).flow
The ViewModel that triggers the query. currentQuery is restored after process death and therefore calls getSearchResults immediately with the old query.
class SearchNewsViewModel #ViewModelInject constructor(
private val repository: NewsRepository,
#Assisted state: SavedStateHandle
) : ViewModel() {
private val currentQuery = state.getLiveData<String?>("currentQuery")
val newsArticles = currentQuery.switchMap { query ->
repository.getSearchResults(query).asLiveData().cachedIn(viewModelScope)
}
fun searchArticles(query: String) {
currentQuery.value = query
}
}
I've just finished my first Android App. It works as it should but, as you can imagine, there's a lot of spaghetti code and lack of performance. From what I've learned on Android and Kotlin language making this project (and a lot of articles/tutorials/SO answers) I'm trying to start it again from scratch to realize a better version. For now I'd like to keep it as simple as possible, just to better understand how to handle API calls with Retrofit and MVVM pattern, so no Volley/RXjava/Dagger etc.
I'm starting from the login obviously; I would like to make a post request to simply compare the credentials, wait for the response and, if positive, show a "loading screen" while fetching and processing data to show in the home page. I'm not storing any information so I have realized a singleton class that holds data as long as the app is running (btw, is there another way to do it?).
RetrofitService
private val retrofitService = Retrofit.Builder()
.addConverterFactory(
GsonConverterFactory
.create(
GsonBuilder()
.excludeFieldsWithoutExposeAnnotation()
.setLenient().setDateFormat("yyyy-MM-dd")
.create()
)
)
.addConverterFactory(RetrofitConverter.create())
.baseUrl(BASE_URL)
.build()
`object ApiObject {
val retrofitService: ApiInterface by lazy {
retrofitBuilder.create(ApiInterface::class.java) }
}
ApiInterface
interface ApiInterface {
#GET("workstation/{date}")
suspend fun getWorkstations(
#Path("date") date: Date
): List<Workstation>
#GET("reservation/{date}")
suspend fun getReservations(
#Path("date") date: Date
): List<Reservation>
#GET("user")
suspend fun getUsers(): List<User>
#GET("user/login")
suspend fun validateLoginCredentials(
#Query("username") username: String,
#Query("password") password: String
): Response<User>
ApiResponse
sealed class ApiResponse<T> {
companion object {
fun <T> create(response: Response<T>): ApiResponse<T> {
return if(response.isSuccessful) {
val body = response.body()
// Empty body
if (body == null || response.code() == 204) {
ApiSuccessEmptyResponse()
} else {
ApiSuccessResponse(body)
}
} else {
val msg = response.errorBody()?.string()
val errorMessage = if(msg.isNullOrEmpty()) {
response.message()
} else {
msg.let {
return#let JSONObject(it).getString("message")
}
}
ApiErrorResponse(errorMessage ?: "Unknown error")
}
}
}
}
class ApiSuccessResponse<T>(val data: T): ApiResponse<T>()
class ApiSuccessEmptyResponse<T>: ApiResponse<T>()
class ApiErrorResponse<T>(val errorMessage: String): ApiResponse<T>()
Repository
class Repository {
companion object {
private var instance: Repository? = null
fun getInstance(): Repository {
if (instance == null)
instance = Repository()
return instance!!
}
}
private var singletonClass = SingletonClass.getInstance()
suspend fun validateLoginCredentials(username: String, password: String) {
withContext(Dispatchers.IO) {
val result: Response<User>?
try {
result = ApiObject.retrofitService.validateLoginCredentials(username, password)
when (val response = ApiResponse.create(result)) {
is ApiSuccessResponse -> {
singletonClass.loggedUser = response.data
}
is ApiSuccessEmptyResponse -> throw Exception("Something went wrong")
is ApiErrorResponse -> throw Exception(response.errorMessage)
}
} catch (error: Exception) {
throw error
}
}
}
suspend fun getWorkstationsListFromService(date: Date) {
withContext(Dispatchers.IO) {
val workstationsListResult: List<Workstation>
try {
workstationsListResult = ApiObject.retrofitService.getWorkstations(date)
singletonClass.rWorkstationsList.postValue(workstationsListResult)
} catch (error: Exception) {
throw error
}
}
}
suspend fun getReservationsListFromService(date: Date) {
withContext(Dispatchers.IO) {
val reservationsListResult: List<Reservation>
try {
reservationsListResult = ApiObject.retrofitService.getReservations(date)
singletonClass.rReservationsList.postValue(reservationsListResult)
} catch (error: Exception) {
throw error
}
}
}
suspend fun getUsersListFromService() {
withContext(Dispatchers.IO) {
val usersListResult: List<User>
try {
usersListResult = ApiObject.retrofitService.getUsers()
singletonClass.rUsersList.postValue(usersListResult.let { usersList ->
usersList.filterNot { user -> user.username == "admin" }
.sortedWith(Comparator { x, y -> x.surname.compareTo(y.surname) })
})
} catch (error: Exception) {
throw error
}
}
}
SingletonClass
const val FAILED = 0
const val COMPLETED = 1
const val RUNNING = 2
class SingletonClass private constructor() {
companion object {
private var instance: SingletonClass? = null
fun getInstance(): SingletonClass {
if (instance == null)
instance = SingletonClass()
return instance!!
}
}
//User
var loggedUser: User? = null
//Workstations List
val rWorkstationsList = MutableLiveData<List<Workstation>>()
//Reservations List
val rReservationsList = MutableLiveData<List<Reservation>>()
//Users List
val rUsersList = MutableLiveData<List<User>>()
}
ViewModel
class ViewModel : ViewModel() {
private val singletonClass = SingletonClass.getInstance()
private val repository = Repository.getInstance()
//MutableLiveData
//Login
private val _loadingStatus = MutableLiveData<Boolean>()
val loadingStatus: LiveData<Boolean>
get() = _loadingStatus
private val _successfulAuthenticationStatus = MutableLiveData<Boolean>()
val successfulAuthenticationStatus: LiveData<Boolean>
get() = _successfulAuthenticationStatus
//Data fetch
private val _listsLoadingStatus = MutableLiveData<Int>()
val listsLoadingStatus: LiveData<Int>
get() = _listsLoadingStatus
private val _errorMessage = MutableLiveData<String>()
val errorMessage: LiveData<String>
get() = _errorMessage
fun onLoginClicked(username: String, password: String) {
launchLoginAuthentication {
repository.validateLoginCredentials(username, password)
}
}
private fun launchLoginAuthentication(block: suspend () -> Unit): Job {
return viewModelScope.launch {
try {
_loadingStatus.value = true
block()
} catch (error: Exception) {
_errorMessage.postValue(error.message)
} finally {
_loadingStatus.value = false
if (singletonClass.loggedUser != null)
_successfulAuthenticationStatus.value = true
}
}
}
fun onLoginPerformed() {
val date = Calendar.getInstance().time
launchListsFetch {
//how to start these all at the same time? Then wait until their competion
//and call the two methods below?
repository.getReservationsListFromService(date)
repository.getWorkstationsListFromService(date)
repository.getUsersListFromService()
}
}
private fun launchListsFetch(block: suspend () -> Unit): Job {
return viewModelScope.async {
try {
_listsLoadingStatus.value = RUNNING
block()
} catch (error: Exception) {
_listsLoadingStatus.value = FAILED
_errorMessage.postValue(error.message)
} finally {
//I'd like to perform these operations at the same time
prepareWorkstationsList()
prepareReservationsList()
//and, when both completed, set this value
_listsLoadingStatus.value = COMPLETED
}
}
}
fun onToastShown() {
_errorMessage.value = null
}
}
LoginActivity
class LoginActivity : AppCompatActivity() {
private val viewModel: LoginViewModel
get() = ViewModelProviders.of(this).get(LoginViewModel::class.java)
private val loadingFragment = LoadingDialogFragment()
var username = ""
var password = ""
private lateinit var loginButton: Button
lateinit var context: Context
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_login)
loginButton = findViewById(R.id.login_button)
loginButton.setOnClickListener {
username = login_username.text.toString().trim()
password = login_password.text.toString().trim()
viewModel.onLoginClicked(username, password.toMD5())
}
viewModel.loadingStatus.observe(this, Observer { value ->
value?.let { show ->
progress_bar_login.visibility = if (show) View.VISIBLE else View.GONE
}
})
viewModel.successfulAuthenticationStatus.observe(this, Observer { successfullyLogged ->
successfullyLogged?.let {
loadingFragment.setStyle(DialogFragment.STYLE_NORMAL, R.style.CustomLoadingDialogFragment)
if (successfullyLogged) {
loadingFragment.show(supportFragmentManager, "loadingFragment")
viewModel.onLoginPerformed()
} else {
login_password.text.clear()
login_password.isFocused
password = ""
}
}
})
viewModel.listsLoadingStatus.observe(this, Observer { loadingResult ->
loadingResult?.let {
when (loadingResult) {
COMPLETED -> {
val intent = Intent(this, MainActivity::class.java)
startActivity(intent)
setResult(Activity.RESULT_OK)
finish()
}
FAILED -> {
loadingFragment.changeText("Error")
loadingFragment.showProgressBar(false)
loadingFragment.showRetryButton(true)
}
}
}
})
viewModel.errorMessage.observe(this, Observer { value ->
value?.let { message ->
Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
viewModel.onToastShown()
}
})
}
Basically what I'm trying to do is to send username and password, show a progress bar while waiting for the result (if successful the logged user object is returned, otherwise a toast with the error message is shown), hide the progress bar and show the loading fragment. While showing the loading fragment start 3 async network calls and wait for their completion; when the third call is completed start the methods to elaborate the data and, when both done, start the next activity.
It seems to all works just fine, but debugging I've noticed the flow (basically network calls start/wait/onCompletion) is not at all like what I've described above. There's something to fix in the ViewModel, I guess, but I can't figure out what