Giter Site home page Giter Site logo

mychat's People

Contributors

hey-deepak avatar hi-deepak avatar lavishswarnkar avatar

Stargazers

 avatar  avatar  avatar

Watchers

 avatar  avatar

Forkers

officialak0609

mychat's Issues

UI itself is preparing the data for it (instead of ViewModel)

Following code segments needs attention :

for (i in messagesViewModel.allMessagesState.indices) {
val message = messagesViewModel.allMessagesState[i]
if (
i + 1 <= messagesViewModel.allMessagesState.size - 1 &&
messagesViewModel.allMessagesState[i].timestamp.toDate().date !=
messagesViewModel.allMessagesState[i + 1].timestamp.toDate().date
) {
item {
MessageCard(message = message, sharedViewModel = sharedViewModel)
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.Center
) {
Text(
text = SimpleDateFormat(
"dd MMMM",
Locale.US
).format(messagesViewModel.allMessagesState[i + 1].timestamp.toDate()),
fontWeight = FontWeight.Medium
)
}
}
} else {
item {
MessageCard(message = message, sharedViewModel = sharedViewModel)
}
}
}

text = SimpleDateFormat(
"h:mm a",
Locale.US
).format(message.timestamp.toDate()),

val isRight = sharedViewModel.senderProfile!!.mailId == message.senderMailId

All the above code comes under "preparing the data for UI" and it is the responsibility of ViewModel to do so. Composables are supposed to just render the prepared data & not prepare data (which in your case it is doing).


Why is this a big deal?

  • Because this code gets executed on the Main thread affecting the UI performance. If you do so much of such tasks, you might see system logs like "I/Choreographer: Skipped <number> frames! The application may be doing too much work on its main thread."

  • Because it is performed inside a composable. Composable are very dynamic & reactive to state changes. A small change (like sending a new message) might trigger a recomposition multiple times. And on every recomposition, your code gets executed (even though no data related to dates might have changed).

Note : You can add logs under for loop and observe the number of times this data processing happens! Now imagine the amount of processing that might happen when there are 1000s of messages (& no pagination).


Even worse situation!

A far worse thing happens when user receives a new message while the screen is open i.e. the snapshotListener is triggered. The complete messages list is recreated i.e. new objects at new addresses are created :

listOfMessages =
snapshot.toObject(Messages::class.java)?.messageArray ?: emptyList()

And guess what... Compose thinks that the data is changed when address is changed even though the content remains the same. As a result of which Above Code segments No.2 & No.3 get re-executed, that too on the Main thread.

These things might seem small but they have the potential to cause your app to slow down because Compound interest applies here also!


What's the solution then?

As a rule of thumb, always prepare all the major data for UI inside ViewModel and pass it "as-is" to the Composable. Make sure that the composable need not do any transformation on the data it receives.

Recall that we have 3 layers : Domain, Data & UI. It is not necessary that UI has to use Domain models "as-is". UI can have models of its own based on requirements. Example: You have Message.Kt model in Domain layer but in addition to these message models, you also need dates in UI layer. So, you can have a new UI model like this :

sealed class MessageUiModel {
   class Message(message: String?, imageUrl: String?, type: MessageType, time: String): MessageUiModel()
   class Date(date: String): MessageUiModel()
}


enum class MessageType {
    SENT, RECEIVED
}

And then ViewModel needs to transform listOf<Message> to listOf<MessageUiModel> & then pass it to the composable.

Note : Make sure that this transformation happens only once when the screen is loaded. When a new message is sent, DO NOT transform the entire data, instead just transform the last sent message & add it to the existing list.

However, because of the architecture you have chosen for saving messages in Firestore (all in one document), we can't avoid the useless re-transformation of the entire list when a new message is received. If you can find a way to avoid this, it would be great!

Poor Error Handling

Currently if any exception occurs in ViewModel, then the app crashes, which is bad experience for user. User must be notified of the error.
In case of error, dialog should appear displaying the error message to user & an optional Retry button (if applicable).
You can use flow to emit ErrorUiEvent to the Activity & then activity can display the dialog.

Login state not saved

Login → DON'T save profile → go back → reopen app → need to relogin.

Login state should be saved. On reopening, ProfileScreen should be shown if profile is not yet created

Remove runBlocking { } calls

runBlocking calls hangs the UI. It should never be used in Android apps.

I understand that you want to wait until the IO operation completes and then navigate to another screen.

A better approach to the same thing without runBlocking is to pass the navController or a lambda (onComplete) to the function, do the IO task in viewModelScope & then invoke the lambda or navigate after the IO task completes.

runBlocking {
senderMailIdState.value = userRepository.getProfileFromPrefs().mailId
}

runBlocking {
Log.d("TAG 9.5", "Mainviewmodel, Create Profile ")
val uri = Uri.parse(profile.displayPhoto)
val downloadedUrl = serverRepository.uploadProfilePicture(imageUriState.value!!, profile)
Log.d("TAG", "createProfile: $downloadedUrl")
serverRepository.createProfile(profile.copy(displayPhoto = downloadedUrl))
saveLoginStatusToPrefs(true)
Log.d("TAG 9.5.2", "Inside MainViewModel createProfile End")
}

runBlocking {
loginStatusState.value = userRepository.getLoginStatusFromPrefs()
Log.d("TAG 9.3", loginStatusState.toString())
}

runBlocking {
val email = it.email!!
val displayPhoto = it.photoUrl.toString()

Only 1 God ViewModel - Refactor MainViewModel

Problem :

Currently there is only 1 god ViewModel in the entire app that is doing all the operations for all the screens. The problems with this approach are :

  • ViewModel becomes complex (+ more lines of code), hard to understand & modify
  • Large number of states as it hosts state for more than one screens
  • More states in memory than required e.g. - Login states are still in memory when the user is on MessagesScreen

Solution :

Divide the MainViewModel into multiple ViewModels - one for each screen:

  • LoginViewModel
  • ProfileViewModel
  • AllUsersViewModel
  • MessagesViewModel

Now that the viewModel is divided, data sharing between screens will stop. In that case, you can use navigation arguments to pass data from one sceen to another.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.