1

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?
Alin
  • 14,809
  • 40
  • 129
  • 218
  • 1
    Are you using the Repository because you are studying the same or you felt the need for it so you included it ? Because you are using Repository in your Service which is violating the MVVM pattern which i guess you are trying to follow . – Karunesh Palekar Aug 28 '21 at 07:02
  • Thank you for your reply. I thought of using the repository since it's single instance, handled by DI, so single source of truth. What would be the MVVM way of handling this service <-> fragment communication? – Alin Aug 28 '21 at 09:26
  • Why would injecting a repository in Service contradict MVVM? isn't a service quite similar to a ViewModel after all? – Alin Aug 28 '21 at 09:57

1 Answers1

2

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

Karunesh Palekar
  • 2,190
  • 1
  • 9
  • 18
  • I really appreciate you took your time to answer me. I do plan to use sealed classes as I also want to be able to pass some parameters. However, the way recommended to bind to the service feels like I'm going back to the times of android gingerbread. Surely there are better ways, more modern. – Alin Aug 28 '21 at 19:56
  • The video was only for the purpose of demonstration about how to attach your services in MVVM . I understand your opinion , but I feel like since the where the Services are to be placed in the MVVM pattern has always been a controversy , it is better to follow those tutorials , and that guy of whose video you might have seen , is a well legit android developer and has a great knowledge about the same . So in such cases , you should implement it in the same manner or the manner of your own and conduct test on your architecture and judge based on results . – Karunesh Palekar Aug 29 '21 at 04:50
  • Also , Is that StateFlow issue resolved ? – Karunesh Palekar Aug 29 '21 at 04:50
  • 1
    I've made a few changes and in the end StateFlow works. What I did, was take out the part with state and put it in a separate class and inject that both in Service and FragmentViewModel. Them made service extend LifecycleService and collect the state flow when needed. I do get it that the Bind is made for that, binding service and activity buy I want to avoid coupling and have something a bit more easy to maintain. I will mark your answer because you have pointed me in the right direction and notified me that repository should not be injected in service. – Alin Aug 30 '21 at 21:13
  • 1
    Happy it helped . Also thanks for describing how you have sorted it until now , will help me in the future – Karunesh Palekar Aug 31 '21 at 04:35