Using Flow to "talk" between service and fragment - android

After more than 2 years, I am "updating" myself with android/kotlin changes, and boy, has it changed a lot.
Scenario
I have a main activity with MyFragment and a MyFragmentViewModel
I have a foreground service MyService
I have a repository that has a Flow<MyState> which should be collected by both MyFragmentViewModel and MySerice
Basically in the past, when I wanted to communicate between a not exported service and the main activity I've used LocalBroadCastReceiver which worked really well and removed the tight coupling between the two. Now that is deprecated so I thought why not have in the Repository a Flow that gets collected whenever it changes, so any client can react to changes.
Here is, for the sake of simplicity, some basic code related
enum class MyState{
STATE_LOADING,
STATE_NORMAL,
....
}
class MyRepository(){
//for simplicity there is no private immutable _state for now
val state:MutableStateFlow<MyState> = MutableStateFlow(MyState.STATE_NORMAL)
fun updateState(newState: MyState){
state.value = newState
}
}
class MyFragmentViewModel #Inject constructor(
private val myRepository: MyRepository
): ViewModel(){
fun updateCurrentState(){
myRepository.updateState(MyState.STATE_LOADING)
}
}
#AndroidEntryPoint
class MyService:Service(){
#Inject lateinitvar myRepository: MyRepository
private val myJob = SupervisorJob()
private val myServiceScope = CoroutineScope(Dispachers.IO+myJob)
fun listenForState(){
myServiceScope.launch{
myRepository.state.collect{
when(it)
....
}
}
}
}
What happens is that on starting, the collect in MyService does get the initial value STATE_NORMAL but when from MyFragmentViewModel I update the MyRepository state, this value is not received by the service.
My questions:
what am I doing wrong? Is something related to service scope/coroutines and how collect works?
is this a good approach, architecturally speaking or are there better way to do it?

Your Services should never communicate with the Repository , since it should come under the UI Module and thus it must communicate to the ViewModel which further communicates to the Repository .
You can read my answer on MVVM pattern here :
Is this proper Android MVVM design?
. I have explaind the MVVM pattern here .
Also for your specific useCase , I recommend you to check this github - project :
https://github.com/mitchtabian/Bound-Services-with-MVVM
In the ReadMe section there is a link to a Youtube video which will explain you in depth about how to use Services with MVVM .
Also in your code , you have made use of enum classes which is not wrong , but since you are using you can make use of Sealed Classes , which is built on top of Enums and provides to maintain strict hierarchy .Your enum class in the form of Sealed Class will look in the following manner :
sealed class MyState{
object State_Loading : MyState()
object State_Normal : MyState()
}
And for you issue about not able to update the data , I suggest you to try
fun updateState(newState: MyState){
state.emit( newState)
}
If this does not work , you need to debug at every step from where the data passes using Log and know where is the error taking place

Related

I want to use Kotlin Flow to update my UI when SharedPreferences have changed

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
}

How to separate data handling from Activity

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".

Dao in Usecase. MVVM or Clean Architecture anti-pattern?

In our "SearchUsecase" we have access to "ShowFtsDao" directly.
Does it violate the Clean Architecture principles? Does it violate the MVVM architecture?
Assuming our intention is to develop a well-built, standard structure, is there anything wrong with this piece of code?
class SearchUsecase #Inject constructor(
private val searchRepository: SearchRepository,
private val showFtsDao: ShowFtsDao,
private val dispatchers: AppCoroutineDispatchers
) : SuspendingWorkInteractor<SearchShows.Params, List<ShowDetailed>>() {
override suspend fun doWork(params: Params): List<ShowDetailed> {
return withContext(dispatchers.io) {
val remoteResults = searchRepository.search(params.query)
if (remoteResults.isNotEmpty()) {
remoteResults
} else {
when {
params.query.isNotBlank() -> showFtsDao.search("*$params.query*")
else -> emptyList()
}
}
}
}
data class Params(val query: String)
}
I believe your use case handles more logic than it needs to.
As a simple explanation I like to think about the components this way:
Sources: RemoteSource (networking), LocalSource (db), optionally MemorySource are an abstraction over your database and networking api and they do the IO thread switching & data mapping (which comes in handy on big projects, where the backend is not exactly mobile driven)
Repository: communicates with the sources, he is responsible for deciding where do you get the data from. I believe in your case if the RemoteSource returns empty data, then you get it from the LocalSource. (you can expose of course different methods like get() or fetch(), where the consumer specifies if it wants the latest data and based on that the repository calls the correct Source.
UseCases: Talk with multiple repositories and combine their data.
Yes, it does.
Because your domain module has access to the data module and actually you've violated the dependency rule.
That rule specifies that something declared in an outer circle must
not be mentioned in the code by an inner circle.
Domain layer must contain interfaces for details (Repositories) which are implemented in the data layer,
and then, they could be injected into the UseCases (DIP).

How to test a method in viewModel which returns LiveData value from repository

In my ViewModel I am calling a method from my repository class which returns a LiveData from a webservice. My viewModel code:
class MainViewModel #Inject constructor(val mainRepository: MainRepository) : ViewModel() {
val source: LiveData<My_Result> = mainRepository.fetchApiresultFromClient(str_query)
.......... }
My question is that is there a way to get real data from the webservice called in repository or do I just prepare the result and assert that its not null; something like below:
when(mainrepository.fetchApiresultFromClient(any(String::class))).thenReturn(myPreparedLiveData<My_result>)
As the question is tagged as unit-testing, IMHO, expecting real data from a web service does not actually fall into the scope of unit-testing. You might call than an integration testing, however, from the unit-testing point of view, you might consider mocking the response from the function that calls the web service and verify if the method was called using proper arguments that you expect.

Edit livedata from non UI thread in kotlin

I am designing a application with a bluetooth connection for displaying some data that is received from the BT. I want to use androids LiveData for communicating between layouts and classes.
I have a dedicated (non UI) thread for managing the connection with the bluetooth adapter that is responsible for receiving and sending data. I now have a problem because I cannot edit the LiveData from that thread. I have the following code for editing LiveData:
class ConnectThread(device: BluetoothDevice): Thread()
{
...
private lateinit var model: MainViewModel
override fun run() {
model = ViewModelProviders.of(this).get(MainViewModel::class.java)
model.frontleft.postValue("hello")
...
}
}
I can edit the LiveData with the upper code in my activity with a layout (UI) without a problem. But in my Connect thread I get the following error on the .of(this) section of the model code:
error .of
This is my ViewModel:
class MainViewModel : ViewModel() {
val frontleft: MutableLiveData<String> by lazy {
MutableLiveData<String>()
}
}
I have made quite a research about my problem but cannot find my answer. Is it because I want to edit the LiveData from non UI thread/fregment/activity? or because the Thread is running on a different part of the code?
this points to current context so if your code are in fragment or Activity you can access it's context like this this#YourFragmentName, this#YuorActivityName you can also check ViewModelProviders documentation
ViewModelProviders.of(this).get(MainViewModel::class.java) change this here
ViewModelProviders.of(this).get(model::class.java) internally uses a retainFragment. You need to send the value to the constructor someway or set a public observable property to set a property from the thread and use that property in the viewmodel.
var myObservable by Delegates.Observables(""){_,_,_ -> "hello"}
something like this

Categories

Resources