So basically up until now I have been using rxjava2 extensively in the applications, but decided to check out data binding, view models and live data. And Im not sure I've got all of this right, because apart from saving state during rotation of device I do not see any other clear benefits of switching, I could even say that I see downsides of introducing data binding with view model between view and rx java powered requests.
Lets see example of some registration form. It would contain:
2 inputs - name and surname
Field with 3 choices
Button with progress
In the reactive world I would have two observables with name and surname, one observable that would merge 3 choices clicks and map them to the right enum, then I could combine all the data together, communicate directly with my single responsible for sending the data in between I would have state with progress or error and tada Im done.
And here is the thing that I came up with using data binding and view models:
class LiveDataViewModel : ViewModel() {
enum class Choice {
NONE, FIRST, SECOND, THIRD
}
private val _progressVisibilityLiveData = MutableLiveData<Boolean>()
private val _errorLiveData = MutableLiveData<GlobalError>()
val progressVisibilityLiveData: LiveData<Boolean> = _progressVisibilityLiveData.apply { value = false }
val errorLiveData: LiveData<GlobalError> = _errorLiveData
val data = LiveDataData()
val observableData = ObservableField(LiveDataData())
fun actionContinue() {
_progressVisibilityLiveData.postValue(true)
if (observableData.get()?.isValid() == false) _errorLiveData.postValue(GlobalError.AllFieldsRequired)
else sendToApi()
}
private fun sendToApi() {
// TODO there would be still an rx java call to single, when we would handle error in the same way we are doing
// it in actionContinue
}
data class LiveDataData(val firstName: ObservableField<String> = ObservableField(""),
val secondName: ObservableField<String> = ObservableField(""),
val choice: ObservableField<Choice> = ObservableField(Choice.NONE)) {
fun changeChoice(newChoice: Choice) {
choice.set(newChoice)
}
fun isValid(): Boolean = !firstName.get().isNullOrEmpty() && !secondName.get().isNullOrEmpty() && choice.get() != Choice.NONE
fun toRequest(): Request = Request(firstName.get()!!, secondName.get()!!, choice.get()!!)
}
}
So I would change fields of my LiveDataData directly from xml using bindData, also I would change state of my selection box depending on this binding too, progress would have to be done manually and then it would trigger the visibility using data binding. But is it really a good way of handling such cases?
The disadvantages I see are that the whole logic in actionContinue would be manually changing values, the values from ObservableProperties could be null, so we either have to handle nullable values everywhere of we have to use !! and to be honest Im not feeling that this is the right direction.
Maybe any of you guys have thought about something similar and could eventually point me if I made some wrong assumptions or if I shouldn't use for example ObservableProperty at all. Obviously there are tons of articles about data binding and live data etc, but I haven't found any that would satisfy my curiosity. Oh and create MutableLiveData for each property from form is not an option.
RxJava is a completely different concept than DataBinding. It's more of a way of handling concurrency than it is about binding data. I 100% think it's worth learning. The Android community has embraced it with open arms.
Shameless plug: I compiled a list of RxJava resources awhile back - http://gregloesch.com/dev/2014/10/20/resources-for-learning-rxjava-android.html
Related
I have a BaseViewModel that basically has the function to get the user data like so:
abstract class BaseViewModel(
private val repository: BaseRepository
) : ViewModel() {
private var _userResponse: MutableLiveData<Resource<UserResponse>> = MutableLiveData()
val userResponse: LiveData<Resource<UserResponse>> get() = _userResponse
fun getUserData() = viewModelScope.launch {
_userResponse.value = Resource.Loading
_userResponse.value = repository.getLoggedInUserData()
}
}
In my Fragment, I access this data by just calling viewModel.getUserData(). This works. However, I'd like to now be able to edit the data. For example, the data class of UserResponse looks like this:
data class UserResponse(
var id: Int,
var username: String,
var email: String
)
In other fragments, I'd like to edit username and email for example. How do I do access the UserResponse object and edit it? Is this a good way of doing things? The getUserData should be accessed everywhere and that is why I'm including it in the abstract BaseViewModel. Whenever the UserResponse is null, I do the following check:
if (viewModel.userResponse.value == null) {
viewModel.getUserData()
}
If you want to be able to edit the data in userResponse, really what you're talking about is changing the value it holds, right? The best way to do that is through the ViewModel itself:
abstract class BaseViewModel(
private val repository: BaseRepository
) : ViewModel() {
private var _userResponse: MutableLiveData<Resource<UserResponse>> = MutableLiveData()
val userResponse: LiveData<Resource<UserResponse>> get() = _userResponse
fun setUserResponse(response: UserResponse) {
_userResponse.value = response
}
...
}
This has a few advantages - first, the view model is responsible for holding and managing the data, and provides an interface for reading, observing, and updating it. Rather than having lots of places where the data is manipulated, those places just call this one function instead. That makes it a lot easier to change things later, if you need to - the code that calls the function might not need to change at all!
This also means that you can expand the update logic more easily, since it's all centralised in the VM. Need to write the new value to a SavedStateHandle, so it's not lost if the app goes to the background? Just throw that in the update function. Maybe persist it to a database? Throw that in. None of the callers need to know what's happening in there
The other advantage is you're actually setting a new value on the LiveData, which means your update behaviour is consistent and predictable. If the user response changes (either a whole new one, or a change to the current one) then everything observeing that LiveData sees the update, and can decide what to do with it. It's less brittle than this idea that one change to the current response is "new" and another change is "an update" and observers will only care about one of those and don't need to be notified of the other. Consistency in how changes are handled will avoid bugs being introduced later, and just make it easier to reason about what's going on
There's nothing stopping you from updating the properties of the object held in userResponse, just like there's nothing stopping you from holding a List in a LiveData, and adding elements to that list. Everything with a reference to that object will see the new data, but only if they look at it. The point of LiveData and the observer pattern is to push updates to observers, so they can react to changes (like, say, updating text displayed in a UI). If you change one of the vars in that data class, how are you going to make sure everything that needs to see those changes definitely sees them? How can you ensure that will always happen, as the app gets developed, possibly by other people? The observer pattern is about simplifying that logic - update happens, observers are notified, the end
If you are going to do things this way, then I'd still recommend putting an update function in your VM, and let that update the vars. You get the same benefits - centralising the logic, enabling things like persistence if it ever becomes necessary, etc. It could be as simple as
fun setUserResponse(response: UserResponse) {
_userResponse.value?.run {
id = response.id
username = response.username
email = response.email
}
}
and if you do decide to go with the full observer pattern for all changes later, everything is already calling the function the right way, no need for changes there. Or you could just make separate updateEmail(email: String) etc functions, whatever you want to do. But putting all that logic in the VM is a good idea, it's kinda what it's there for
Oh and you access that object through userResponse.value if you want to poke at it - but like I said, better to do that inside a function in the VM, keep that implementation detail, null-safety etc in one place, so callers don't need to mess with it
The ideal way to update userResponse you should change/edit _userResponse so that your userResponse we'll give you the updated data.
it should be something like this
_userResponse.value = Resource<UserResponse>()
Ok so I want to start using Kotlin-Flow like all the cool kids are doing. It seems like what I want to do meets this reactive pattern. So I receive a Firebase message in the background
...
override fun onMessageReceived(remoteMessage: RemoteMessage) {
super.onMessageReceived(remoteMessage)
val msg = gson.fromJson(remoteMessage.data["data"], MyMessage::class.java)
// persist to SharedPreferences
val flow = flow<MyMessage> { emit(msg) }
and I have a dashboard UI that simply would refresh a banner with this message. Not sure how to observe or collect this message from my DashboardViewModel. Examples and tutorials all seem to emit and collect in the same class. Sounds like I need more direction and more experience here but not much luck finding more real world examples.
Have a look at the Kotlin docs for it: https://kotlinlang.org/docs/flow.html#flows
The basic idea is you create a Flow, and it can produce values over time. You run collect() on that in a coroutine, which allows you to asynchronously handle those updates as they come in.
Generally that flow does a bunch of work internally, and just emits values as it produces them. You could use this within a class as a kind of worker task, but a lot of the time you'd expose flows as a data source, for other components to observe. So you'll see, for example, repositories that return a Flow when you try to get a thing - it's basically "ok we don't have that yet, but it'll come through here".
I'm not an expert on them, and I know there are some caveats about the different builders and flow types, and how you emit to them - it's not always as simple as "create a flow, hand back a reference to it, emit data to it when it comes in". There's actually a callbackFlow builder specially designed around interfacing callbacks with the flow pattern, that's probably worth checking out:
https://developer.android.com/kotlin/flow#callback
The example is about Firebase specifically too - it looks like the idea is broadly that the user requests some data, and you return a flow which internally does a Firebase request and provides a callback. When it gets the data, it uses offer (a special version of emit that handles the callback coming through on a different coroutine context) to output data to the observer. But it's the same general idea - all the work the flow does is encapsulated within it. It's like a task that runs on its own, producing values and outputting them.
Hope that helps! I think once you get the general idea, it's easier to follow the examples, and then understand what the more specialised things like StateFlow and SharedFlow are there for. This might be some helpful reading (from the Android devs):
Lessons learnt using Coroutines Flow - some general "how to use it" ideas, section 4 is about callbacks again and the example might be helpful
Migrating from LiveData to Kotlin’s Flow - some basic patterns you might already be using, especially around UI and LiveData
edit- while I was finding those I saw a new Dev Summit video about Flows and it's pretty good! It's a nice overview of how they work and how to implement them in your app (especially for UI stuff where there are some things to consider): https://youtu.be/fSB6_KE95bU
flow<MyMessage> { emit(msg) } could just be flowOf(msg), but it's weird to wrap a single item in a Flow. If you're making a manual request for a single thing, this is more appropriately handled with a suspend function that returns that thing. You can convert the async callback code to a suspend function with suspendCoroutine(), but Firebase already provides suspend functions you can use instead of callbacks. If you were making repeated requests for data that changes over time, a Flow would be appropriate, but you need to do it higher up by converting the async code using callbackFlow.
In this case, it looks like you are using FirebaseMessagingService, which is an Android Service, and it directly acts as a callback using this onMessageReceived function.
What you possibly could do (and I haven't tried this before), is adapt a local BroadcastReceiver into a Flow you can use from elsewhere in your app. The FirebaseMessangingService can rebroadcast local Intents that can be picked up by such a Flow. So, you could have a function like this that creates a Flow out of a local broadcast.
fun localBroadcastFlow(context: Context, action: String) = callbackFlow {
val receiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
intent.extras?.run(::trySend)
}
}
LocalBroadcastManager.getInstance(context).registerReceiver(receiver, IntentFilter(action))
awaitClose { LocalBroadcastManager.getInstance(context).unregisterReceiver(receiver) }
}
Then in your service, you could expose the flow through a companion object, mapping to your data class type.
class MyMessageService: FirebaseMessagingService() {
companion object {
private const val MESSAGE_ACTION = "mypackage.MyMessageService.MyMessage"
private const val DATA_KEY = "MyMessage key"
private val gson: Gson = TODO()
fun messages(context: Context): Flow<MyMessage> =
localBroadcastFlow(context, MESSAGE_ACTION)
.mapNotNull { bundle ->
val messageData = bundle.getString(DATA_KEY) ?: return#mapNotNull null
gson.fromJson(messageData, MyMessage::class.java)
}
}
override fun onMessageReceived(remoteMessage: RemoteMessage) {
val intent = Intent(MESSAGE_ACTION)
intent.putExtra(DATA_KEY, remoteMessage.data["data"])
LocalBroadcastManager.getInstance(applicationContext).sendBroadcast(intent)
}
}
And then in your Fragment or Activity, you can collect from MyMessageService.messages().
Note that LocalBroadcastManager is recently deprecated because it promotes the practice of exposing data to all layers of your app. I don't really understand why this should be considered always bad. Any broadcast from the system is visible to all layers of your app. Any http address is visible to all layers of your app, etc. They suggest exposing an observable or LiveData as an alternative, but that would still expose the data to all layers of your app.
I created a class that helps me persist my data but also added an observable flow that emits the current message received.
class MessagePersistence(
private val gson: Gson,
context: Context
) {
private val sharedPreferences = context.getSharedPreferences(
"Messaging", MODE_PRIVATE
)
private val _MyMessageFlow = MutableStateFlow<Message?>(null)
var myMessageFlow: StateFlow<Message?> = __MyMessageFlow
data class Message(
val msg: String
)
var message: Message?
get() = sharedPreferences
.getString("MyMessages", null)
?.let { gson.fromJson(it, Message::class.java) }
set(value) = sharedPreferences
.edit()
.putString("MyMessages", value?.let(gson::toJson))
.apply()
_MyMessageFlow.value = message
myMessageFlow = _MyMessageFlow
}
In my viewModel I inject this class through its constructor and define it as
class MyViewModel(
private val messagePersistence: MessagePersistence
) : ViewModel() {
val myMessage = messagePersistence.myMessageFlow
...
}
then in my fragment I can collect it using an observer.
class MyFragment : Fragment() {
...
viewModel.myMessage.observe(viewLifecycleOwner.lifecycleScope) {
'update the UI with new message
}
I have a working Activity (TwalksRouteActivity) that accepts a record id (routeID) from a bundle (passed from a Fragment), pulls the associated record from my repository (routesRepository), and passes an associated value/column (routeName) to my UI. This works fine. However, as I understand best practice (I am learning Android development), the call to my Repository should be in a ViewModel, not an Activity. Is this correct? I have tried but failed to do this myself and would really appreciate some help in how to do this please.
TwalksRouteActivity:
class TwalksRouteActivity() : AppCompatActivity() {
private lateinit var viewModel: RouteViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//Log.i("CWM","Called ViewModelProvider")
//viewModel = ViewModelProvider(this).get(RouteViewModel::class.java)
var bundle: Bundle? = intent.extras
var routeID = bundle?.getInt("routeID")
lifecycleScope.launch (Dispatchers.Main) {
val database = getDatabase(application)
val routesRepository = RoutesRepository(database)
val selectedRoute = routesRepository.getRoute(routeID)
val routeName = selectedRoute.routeName
Log.d("CWM", routeName.toString())
setContentView(R.layout.route_detail)
val routeName_Text: TextView = findViewById(R.id.routeName_text)
routeName_Text.text = routeName.toString()
val routeID_Text: TextView = findViewById(R.id.routeID)
routeID_Text.text = routeID.toString()
}
}
}
You are correct. Best practices include the idea of a ViewModel that handles communications between bussiness logic (your repository) and the activity or fragment which uses or/and dislpays the data. You should check Android Developers ViewModel's official documentation at: ViewModel Overview. Also the guide to app architecture. Check the following image:
As you can see, it describes the data-driven communication flow, and as you said, the ViewModel will call the repository functions that get the data. The ViewModel will then provide the activity with variables and / or functions that can be observed (such as: LiveData), and fire events that the activity will take to make its state changes / data presentation in the UI (this is call reactive pattern).
You should check these Codelabs (free lessons from Google): Incorporate Lifecycle-Aware Components and Android Room with a View - Kotlin (although it mainly covers Room Library, the codelab makes use of ViewModel and Android's best practices recommended by Google). Also, you could check this article: ViewModels and LiveData: Patterns + AntiPatterns.
I could write a lot of code but I think it is beyond the scope of this answer. I'm also learning, and my way was to first understand how these things work and why these things are called "best practices".
I am having a DB populated with weather data, in both imperial and metric units. Now, I have made two different classes which act as a model to get data from the DB. CurrentWeatherMetric has only metric columns, and CurrentWeatherImperial has only imperial fields.
Since I am using MVVM architecture pattern, the ViewModel provides me this data, by calling a function in ViewModel getData(Unit.METRIC) where Unit is an enum class I've made to distinguish the data.
The problem arises here.
My viewModel looks like:
class WeatherViewModel(
private val weatherRepository: WeatherRepositoryImpl
) : ViewModel() {
lateinit var currentWeather: LiveData<CurrentWeather>
lateinit var forecastWeather: LiveData<List<ForecastWeather>>
fun getValuesOfUnit(unit: Unit) {
currentWeather = when (unit) {
Unit.IMPERIAL->weatherRepository.getCurrentWeatherImperial()
Unit.METRIC->weatherRepository.getCurrentWeatherMetric()
}
getWeather()
}
private fun getWeather() {
viewModelScope.launch {
try {
weatherRepository.getWeather()
} catch (e: IOException) {
}
}
}
}
As you can see, lateinit var currentWeather: LiveData<CurrentWeather>,
I had to make another class which store the data of the query with units. I made this so that I could easily implement databinding with it. But I feel this is a really wrong way to do things and hence I have asked this question. How can I get rid of that lateinit variable and implement databinding to adapt to any of the data.
In my current data binding layout, I have data field as:
<data>
<variable
name="viewModel"
type="com.mythio.weather.ui.WeatherViewModel" />
</data>
And I bind to views by:
app:someattribute="#{viewModel.currentWeather.temperature}"
If the question title makes a little sense about what I am asking, or seems misleading, please feel free to edit this to make it a better question.
When using MVVM architecture pattern, Google's recommended way is to make ViewModel that handles connection between your data and view, so it contains UI logic as well as some portion of business logic bound to your UI.
Moreover, implementation of ViewModel in recommended way helps you handle UI lifecycle (Activity/Fragments) in better and hassle-free way.
When using data-binding with MVVM, it's good practice to bind ViewModel directly to xml so that, when data changes you can directly reflect it to UI using LiveData without wiring it manually.
Hence, LiveData can be used as Data-Value holder as it's also Lifecycle-aware component.
On the other hand, Repositories are good way to manage business logic and providing "single source of truth" for data driving through app. So, all data sources like local-db, API calls, shared-preferences etc. should be accessed via repository.
So, yes!! Things you're doing are good & you're on the right track while following MVVM Architecture Pattern.
Note: You can refer here for more info and some improvements in your code.
I'm working on an Android app and I want to implement the MVVM pattern, which is pretty much the standard pushed by Google, however, I'd like to avoid using Android Data Bindings library if possible, since I hate autogenerated XML magic.
I've tried to implement something essentially akin to databinding in RxJava (Kotlin) using Jake Wharton's data binding library, plus some helpful extension methods.
My question is, is this the right way to go about things? Is this good enough to use in production? Are there potential problems I'm not seeing with this approach that will pop up later?
Essentially, I've implemented it like this:
I have a MvvmFragment (there is a similar class for activities) which takes care of setting up and managing the lifecycle of a CompositeDisposable object.
Then, in my ViewModel (part of the android Arch ViewModel package) I have all of the fields that will be bound to declared like this:
var displayName = BindableVar("")
var email = BindableVar("")
var signInProvider = BindableVar<AuthProvider>(defaultValue = AuthProvider.PASSWORD)
(Side note - Since Rx doesn't allow null values, I'm not sure how to handle the case of defaults for objects where the concept of a default doesn't really make sense, such as the AuthProvider above)
The BindableVar class is implemented like this:
class BindableVar<T>(defaultValue: T) {
var value: T = defaultValue
set(value) {
field = value
observable.onNext(value)
}
var observable = BehaviorSubject.createDefault(value)!!
}
Using Jake Wharton's RxBindings library, I have created some helpful extension methods on top of that, such as:
fun Disposable.addTo(compositeDisposable: CompositeDisposable): Disposable {
compositeDisposable.add(this)
return this
}
fun TextView.bindTextTo(string: BindableVar<String>): Disposable {
return string.observable.subscribe(this.text())
}
fun View.bindVisibilityTo(visibility: Int) {
// ... not shown
}
fun ImageView.bindImageUriTo(
src: BindableVar<Uri>, #DrawableRes placeholder: Int? = null
): Disposable {
return if (placeholder == null) {
src.observable.subscribe {
GlideApp.with(context).load(it).into(this)
}
} else {
src.observable.subscribe {
GlideApp.with(context).load(it).placeholder(placeholder).into(this)
}
}
}
Using these extension methods, I then obtain the ViewModel instance on Fragment initialization, and call a method initBindings(), which looks something like this:
item_display_name_value.bindTextTo(viewModel.displayName).addTo(bindings)
item_email_address_value.bindTextTo(viewModel.email).addTo(bindings)
item_profile_picture_view.bindImageUrlTo(viewModel.avatarUrl).addTo(bindings)
I want to avoid getting a week into fleshing out this architecture and then suddenly realizing there is some critical problem that can't be solved easily, or some other hidden gotcha. Should I just go with XML based data binding? I've heard a lot of complaints about the difficulty of unit-testing it, and the difficulty of reusing code with it.