Are there any cons of using Kotlin state flow in companion object for checking if background service is running?
I wanted to use ActivityManager and its function isServiceRunning(Service::class.name), but this method is deprecated.
By the way, I should be observing this service from Fragment/Fragment's ViewModel, so I wanted to avoid Binding the service to Activity.
class TestService : Service() {
companion object {
private val _testStateFlow = MutableStateFlow<TestServiceEvent>()
val testServiceStateFlow = _testStateFlow.asStateFlow()
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
_testStateFlow.value = TestServiceEvent.ServiceRunning(true)
....
....
....
....
_testStateFlow.value = TestServiceEvent.TestTaskDone()
}
override fun onDestroy(){
super.onDestroy()
_testStateFlow.value = TestServiceEvent.ServiceRunning(false)
}
sealed class TestServiceEvent{
data class ServiceRunning(val isRunning : Boolean) : TestServiceEvent()
object TestTaskDone : TestServiceEvent()
}
}
Related
I have a started service app. It intent to activity from another app, but still running in foreground. After a button click in that activity, I want to send data (for example a string "potato") to service without startService() in order to continue, not restart. That's how service keeps running till get the data, while(requiredData != "potato"){}.start. How can I send it, or return response ? I think to use Messenger or Broadcast, but I'm not sure it fits well and how to do.
Note: Service App connected to an activity from another app.
Service App
class RegistryService : Service() {
override fun onBind(p0: Intent?): IBinder? = null
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
val i = packageManager.getLaunchIntentForPackage("com.myexample.potatoactivity")
if (i!=null) {
i.flags = Intent.FLAG_ACTIVITY_NEW_TASK
startActivity(i)
} else {
Toast.makeText(this,"Fail",Toast.LENGTH_SHORT).show()
}
while (true) { // requiredData != "potato"
//Log.d("MyService", "Wait for potato")
}
return START_STICKY
}
}
Potato Activity
class PotatoActivity : AppCompatActivity() {
private lateinit var binding: ActivityPotatoBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityPotatoBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
binding.buttonSendData.setOnClickListener {
//it.putExtra("REQUIRED_DATA", "potato")
}
}
}
I want to use MutableSharedFlow in the Service class, but I'm not sure how to stop subscribing when Service ends. How to implement the MutableSharedFlow function in service or any other function available to listen to stream data?
To use a Flow in an android Service class we need a CoroutineScope instance to handle launching coroutines and cancellations. Please see the following code with my comments:
class CoroutineService : Service() {
private val scope = CoroutineScope(Dispatchers.IO)
private val flow = MutableSharedFlow<String>(extraBufferCapacity = 64)
override fun onCreate() {
super.onCreate()
// collect data emitted by the Flow
flow.onEach {
// Handle data
}.launchIn(scope)
}
override fun onStartCommand(#Nullable intent: Intent?, flags: Int, startId: Int): Int {
scope.launch {
// retrieve data from Intent and send it to Flow
val messageFromIntent = intent?.let { it.extras?.getString("KEY_MESSAGE")} ?: ""
flow.emit(messageFromIntent)
}
return START_STICKY
}
override fun onBind(intent: Intent?): IBinder? = null
override fun onDestroy() {
scope.cancel() // cancel CoroutineScope and all launched coroutines
}
}
I want to create TCP_Client which sends data to server in many activities. I decided to use Dependency Injection to inject all samely configured clients to all clients. Unfortunately it stops working on start.
My application module
val appModule = module {
single<ConnectionService> { ConnectionServiceTcp("192.168.0.1", 8888) }
}
Main Application
class MainApplication : Application() {
override fun onCreate() {
super.onCreate()
startKoin {
androidContext(this#MainApplication)
androidLogger()
modules(appModule)
}
}
}
class ConnectionServiceTcp(private val ipAddress: String, private val port : Int)
: IntentService("TCP_CLIENT"), ConnectionService {
private var client : Socket? = null
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
super.onStartCommand(intent, flags, startId)
return START_STICKY
}
override fun onHandleIntent(intent: Intent?) {
startTcpServer()
}
private fun startTcpServer() {
client = Socket(ipAddress, port)
}
override fun isConnectedToServer(): Boolean {
Log.println(Log.INFO, null, "Adres = ${client?.localAddress} port = ${client?.localPort}")
return false
}
}
class MainActivity : AppCompatActivity() {
private val connectionService : ConnectionService by inject()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
startTcpServer()
}
private fun startTcpServer() {
val serverTcp = Intent(this, ConnectionServiceTcp::class.java)
startService(serverTcp)
}
And then I receive
java.lang.RuntimeException: Unable to instantiate service connection.impl.ConnectionServiceTcp: java.lang.InstantiationException: java.lang.Class<connection.impl.ConnectionServiceTcp> has no zero argument constructor
I can't find a way to Inject background client for sending TCP requests
Just as with Activities, Fragments or some other platform components, Android system implies that Services should have a single no-arg constructor. The system looks for a default constructor in Service class and calls it using reflection. That's why it's prohibited to add non-default constructors (i.e. constuctors with arguments).
To inject dependencies into Service you should do the same as you do in Activities (declare a field and inject it using by inject() delegate. So the final code will look as follows:
class ConnectionServiceTcp()
: IntentService("TCP_CLIENT"), ConnectionService {
private val ipAddress: String by inject()
private val port : Int by inject()
private var client : Socket? = null
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
super.onStartCommand(intent, flags, startId)
return START_STICKY
}
override fun onHandleIntent(intent: Intent?) {
startTcpServer()
}
private fun startTcpServer() {
client = Socket(ipAddress, port)
}
override fun isConnectedToServer(): Boolean {
Log.println(Log.INFO, null, "Adres = ${client?.localAddress} port = ${client?.localPort}")
return false
}
}
I have an Interface that I use as the common data source for my RecyclerView Adapters, which looks like this:
interface GenericRVAdapterDataSource {
fun getCellCount() : Int
fun getViewModelForCell(position : Int) : CellViewModel
}
Now, I have two other Interfaces that extend this one:
interface GroupHomeDataSource : GenericRVAdapterDataSource {
fun getJoinedGroupsCount() : Int
fun getJoinedGroupViewModel(forIndex : Int) : GroupHomeCellViewModel
override fun getCellCount(): Int = getJoinedGroupsCount()
override fun getViewModelForCell(position: Int): CellViewModel = getJoinedGroupViewModel(position)
}
and:
interface GroupSuggestedDataSource : GenericRVAdapterDataSource {
fun getSuggestedGroupsCellCount() : Int
fun getSuggestedGroupViewModelForCell(atIndex : Int) : GroupHomeCellViewModel
override fun getCellCount(): Int = getSuggestedGroupsCellCount()
override fun getViewModelForCell(position: Int): CellViewModel = getSuggestedGroupViewModelForCell(position)
}
However, when I implement both interfaces into the class:
class GroupHomeViewModel(app : Application) : AndroidViewModel(app), GroupHomeDataSource, GroupSuggestedDataSource, GroupsHomeInteractionLogic {...}
I got the error:
Class 'GroupHomeViewModel' must override public open fun getCellCount(): Int defined in GroupHomeDataSource because it inherits multiple interface methods of it
For now, I've avoided the problem by just storing both interfaces as variables:
val joinedGroupsDataSource = object: GroupHomeDataSource {
override fun getJoinedGroupsCount(): Int = joinedGroupsList.size
override fun getJoinedGroupViewModel(forIndex: Int): GroupHomeCellViewModel = joinedGroupsList[forIndex]
}
val suggestedGroupsDataSource = object: GroupSuggestedDataSource {
override fun getSuggestedGroupsCellCount(): Int = suggestedGroupsList.size
override fun getSuggestedGroupViewModelForCell(atIndex: Int): GroupHomeCellViewModel = suggestedGroupsList[atIndex]
}
However, I'm not sure that's the most effective way to resolve this diamond problem - if I can even call it that.
Do I just do what the compiler tells me to do and implement getCellCount() and redirect it to one of the interfaces' implementations using:
//MARK:- super interface implementation
override fun getCellCount(): Int {
return super<GroupHomeDataSource>.getCellCount()
//Or: return super<GroupSuggestedDataSource>.getCellCount()
}
override fun getViewModelForCell(position: Int): CellViewModel {
return super<GroupHomeDataSource>.getViewModelForCell(position)
//Or: return super<GroupSuggestedDataSource>.getViewModelForCell(position)
}
//ENDMARK
Or do I implement that method while determining which of the interfaces calls for it (is there a method for this)?
The compiler cannot choose between multiple implementations on its own. But, the whole implementation looks a little overwhelmed. Usually you shouldn't create extended DataSource for each case, use a Generic interface instead. If GroupHomeViewModel provides multiple data sources, just create different properties, as you did.
interface CellViewModel
interface GroupHomeCellViewModel : CellViewModel
interface RVAdapterDataSource<T : CellViewModel> {
fun getCellCount() : Int
fun getViewModelForCell(position : Int) : T
}
class ListAdapterDataSource<T : CellViewModel>(
private val list: List<T>
) : RVAdapterDataSource<T> {
override fun getCellCount() = list.size
override fun getViewModelForCell(forIndex: Int) = list[forIndex]
}
class GroupHomeViewModel(
joinedGroupList: List<GroupHomeCellViewModel>,
suggestedGroupList: List<GroupHomeCellViewModel>
) {
val joinedGroupsDataSource = ListAdapterDataSource(joinedGroupList)
val suggestedGroupsDataSource = ListAdapterDataSource(suggestedGroupList)
}
I have a ViewModel that inherits from a base class and I would like to have a corresponding Activity also inherit from a base class. The activity would call the same method of the derived ViewModel each time. So it would be something like this:
BaseViewModel:
abstract class BaseViewModel(application: Application) : AndroidViewModel(application) {
protected val context = getApplication<Application>().applicationContext
protected var speechManager: SpeechRecognizerManager? = null
var _actionToTake : MutableLiveData<AnalyseVoiceResults.Actions> = MutableLiveData()
var actionToTake : LiveData<AnalyseVoiceResults.Actions> = _actionToTake
open fun stopListening() {
if (speechManager != null) {
speechManager?.destroy()
speechManager = null
}
open fun startListening() {
val isListening = speechManager?.ismIsListening() ?: false
if (speechManager == null) {
SetSpeechListener()
} else if (!isListening) {
speechManager?.destroy()
SetSpeechListener()
}
}
}
BaseActivity
class BaseActivity : AppCompatActivity() {
private lateinit var baseViewModel: BaseViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
fun goback() {
super.onBackPressed()
baseViewModel.stopListening()
finish()
}
fun startListening() {
baseViewModel.startListening()
}
override fun onDestroy() {
super.onDestroy()
baseViewModel.stopListening()
}
}
Derived Activity:
class DerivedActivity : BaseActivity() {
private val nextActivityViewModel: NextActivityViewModel by inject()
///^^inherits from BaseViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
/*** pass reference ***/
baseViewModel = nexActivityViewModel
nextActivityViewModel.actionToTake.observe(this, object : Observer<AnalyseVoiceResults.Actions?> {
override fun onChanged(t: AnalyseVoiceResults.Actions?) {
if (t?.equals(AnalyseVoiceResults.Actions.GO_BACK) ?: false) {
goback()
}
}
})
startListening()
}
}
Will this cause memory leaks to have two instances of a view model for this activity? Is there a better way to do this? I don't want to keep repeating the same code for all my activities. (I would also have the same question if I was doing this with one base fragment).
make this var baseViewModel: BaseViewModel an abstract variable where all the children class must override it. So, when you call the startListening and stopListening, these methods will be called from children implementation.
Edit:
Make the BaseActivity an abstract class and the baseViewModel as an abstract variable
abstract class BaseActivity : AppCompatActivity() {
private abstract var baseViewModel: BaseViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
fun goback() {
super.onBackPressed()
baseViewModel.stopListening()
finish()
}
fun startListening() {
baseViewModel.startListening()
}
override fun onDestroy() {
super.onDestroy()
baseViewModel.stopListening()
}
}
So, your DerivedActivity must override the baseViewModel, and every call on father's class will trigger the child
class DerivedActivity : BaseActivity() {
override val baseViewModel: NextActivityViewModel by inject()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Will this cause memory leaks to have two instances of a view model for
this activity?
No, there are no memory leaks with this approach. Nor do you have 2 instances of the ViewModel for the same activity. It's a single instance of ViewModel which is being referenced with different variables in BaseActivity and BaseViewModel.
Is there a better way to do this?
The first issue I see is that you have Android specific code in the ViewModels, which is not considered a good practice. You should move the speech manager code to the base activity itself, and ViewModel should only hold the "state" data that you want to retain on orientation changes. This will ensure all the Speech Management methods (create, resume, destroy) will be in the base activity. Concrete activity will only have observers if the state changes.
If you are following any architecture pattern (like MVP), once you move the Speech Manager code out to activity, it would become obvious to move this further out to the Presenter.
EDIT: I have not used the MVVM pattern in production, but this is a light variant of what you may want:
The basic idea is to move Speech management code in a lifecycle-aware component. All UI code in view/activity and business logic / non-android state in viewmodel. I don't see a point in having base activity or viewmodel based on the requirements you have shared so far.
/**
* All the speech related code is encapsulated here, so any new activity/fragment can use it by registering it's lifecycle
*/
class SpeechManager(private val context: Context): LifecycleObserver {
val TAG = "SpeechManager"
private var speechRecognizer: SpeechRecognizer? = null
fun registerWithLifecycle(lifecycle: Lifecycle) {
Log.e(TAG, "registerWithLifecycle")
lifecycle.addObserver(this)
}
#OnLifecycleEvent(Lifecycle.Event.ON_START)
fun start() {
Log.e(TAG, "start")
speechRecognizer = (speechRecognizer ?: SpeechRecognizer.createSpeechRecognizer(context)).apply {
// setRecognitionListener(object : RecognitionListener {
// //implement methods
// })
}
}
#OnLifecycleEvent(Lifecycle.Event.ON_STOP)
fun stop() {
Log.e(TAG, "stop")
speechRecognizer?.run {
stopListening()
destroy()
}
}
}
ViewModel:
class SpeechViewModel: ViewModel() {
val TAG = "SpeechViewModel"
//List all your "data/state" that needs to be restores across activity restarts
private val actions: MutableLiveData<Actions> = MutableLiveData<Actions>().apply { value = Actions.ActionA }
//Public API for getting observables and all use-cases
fun getActions() = actions
fun doActionA(){
//validations, biz logic
Log.e(TAG, "doActionA")
actions.value = Actions.ActionA
}
fun doActionB(){
Log.e(TAG, "doActionB")
actions.value = Actions.ActionB
}
}
sealed class Actions{
object ActionA: Actions()
object ActionB: Actions()
}
Activity/View:
class SpeechActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_speech)
setSupportActionBar(toolbar)
initSpeechManager()
}
private lateinit var speechManager: SpeechManager
private lateinit var speechViewModel: SpeechViewModel
/**
* Register lifecycle aware components and start observing state changes from ViewModel.
* All UI related code should ideally be here (or your view equivalent in MVVM)
*/
private fun initSpeechManager() {
speechManager = SpeechManager(this).apply {registerWithLifecycle(lifecycle)}
speechViewModel = ViewModelProviders.of(this).get(SpeechViewModel::class.java).apply {
getActions().observe(this#SpeechActivity, Observer<Actions>{
when(it){
is Actions.ActionA -> {
Log.e(TAG, "Perform ActionA")
speechManager.start()
}
is Actions.ActionB -> {
Log.e(TAG, "Perform ActionB")
speechManager.stop()
super.onBackPressed()
}
}
})
}
}
}