I have an activity that, as far as I can tell, is happily binding to a service every time the activity is created. The first time the activity is created, it also starts the service with the startService command, as follows:
private fun attachRecorderService() {
val intent = Intent(this, AudioRecorderService::class.java)
bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE)
if (!mBooleanRecorderServiceStarted) {
startService(intent)
mBooleanRecorderServiceStarted = true
}
}
That activity's fragments all get a reference to that service in their onActivityCreated() function as follows:
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
val parentActivity = activity as MainActivity
mAudioRecorderService = parentActivity.mAudioRecorderService
That works fine the first time the fragment is created, but as soon as the screen is rotated I get an error telling me the service hasn't been initialised in the activity.
lateinit property mAudioRecorderService has not been initialized
As far as I can tell, onActivityCreated() in the fragment is racing with onCreate() in the activity, and trying to get the reference before onCreate() initialises it.
Which I don't understand. I thought onActivityCreated() waited until after onCreate() had completed.
What am I doing wrong? Should I use some sort of callback in the fragment, that only triggers when the activity has bound to the service? I've seen mention of that, but I have no idea how to do it.
The question Communication between Activity and Service deals with Activities and Services. I'm asking about the Fragments that are attached to the Activity, and how they can access a service that the Activity has already bound to.
The service is not available directly after calling bindService. Use a ServiceConnection. When onServiceConnected is called the service is ready to use
private val connection = object : ServiceConnection {
override fun onServiceDisconnected(p0: ComponentName?) {
}
override fun onServiceConnected(p0: ComponentName, binder: IBinder) {
}
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
application.bindService(Intent(application, MyService::class.java), connection, Context.BIND_AUTO_CREATE)
}
Related
I have viewmodel call TestViewModel and a method call fetchDataFromDataSource() to call fetch data from the server, I used to call load data on OnResume() until I bump into lifecycleScope
I have tried to read more but didn't really get which is better.
class TestViewModel: Viewmodel() {
fun fetchDataFromDataSource(){
....
}
}
class TestActivity : AppCompatActivity() {
private val viewModel: TestViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
...
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
// Is it best to call here
viewModel.fetchDataFromDataSource()
}
}
}
onResume(){
super.onResume()
// or is it best to call here
viewModel.fetchDataFromDataSource()
}
}
where is the best place to call fetchDataFromDataSource(), is it in onResume() or lifecycleScope and what is the advantage lifecycleScope has over onResume() or onStart()
I know the view has rendered at onResume() so what benefit does lifecycleScope has over android lifecycle (onResume onCreate onStart...)
repeatOnLifecycle is similar to calling methods on the respective lifecycle events every time the Activity hits that state but with a quick access to the lifecycleScope which can launch a coroutine.
Example:
override fun onResume(){
super.onResume()
viewModel.fetchDataFromDataSource()
}
is equivalent to -
class MainActivity : AppCompatActivity {
init {
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.RESUMED) {
viewModel.fetchDataFromDataSource()
}
}
}
}
If you want to load the data from ViewModel every time the user comes to foreground from background, use onStart or repeatOnLifecycle(Lifecycle.State.STARTED).
If you need to load the data everytime the Activity resumes, then use onResume or the lifecycleScope equivalent as shown above but if this is just a one-time op, consider using onCreate.
The following code gives me error as registering occurs after onResume:
class TempActivity: AppCompatActivity(){
private lateinit var binding: ActivityTempBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityTempBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.tempBtn.setOnClickListener {
val a = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
//SomeCode
}
a.launch(
//SomeIntent
)
}
}
However, if I use activityResultRegistry, I am not getting any errors. The code is
class TempActivity: AppCompatActivity(){
private lateinit var binding: ActivityTempBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityTempBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.tempBtn.setOnClickListener {
val a = activityResultRegistry.register("key", ActivityResultContracts.StartActivityForResult()){
// SomeCode
}
a.launch(
//Some Intent
)
}
}
The latter code run without any problem and launches the corresponding intent. I just want to know how safe is latter one and is there any unwanted behaviors I should be aware of?
It gives you an error because you are registering the contract conditionally after the Activity is well into its lifecycle.
The guide says:
You must always call registerForActivityResult() in the same order for each creation of your fragment or activity to ensure that the inflight results are delivered to the correct callback.
It's clear that if you register something after the Activity is created and it only happens when a condition (click event in this case) is met, the order of registration cannot be ensured.
A better solution would be to register the contract before the Activity is created and just call launch() when you need it. The guide, once again, says it is completely safe:
registerForActivityResult() is safe to call before your fragment or activity is created, allowing it to be used directly when declaring member variables for the returned ActivityResultLauncher instances.
So in your case, the Activity would look like this:
class TempActivity: AppCompatActivity() {
private lateinit var binding: ActivityTempBinding
// registering the contract here
private val a = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
//SomeCode
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityTempBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.tempBtn.setOnClickListener {
// launching the registered contract
a.launch(
//SomeIntent
)
}
}
}
Further explanation:
The registerForActivityResult() is a convenience method that internally calls the registry's register method with an automatically created key. The key is derived from an internal AtomicInteger that is retrieved and incremented every time you call registerForActivityResult(). Since this key is used to look up the callback that will handle the result, every call to the registerForActivityResult must be in the same order, otherwise it might happen that you once call it in the order of A (key=0), B (key=1) but then you call it B (key=0), A (key=1), or not even call the register method for one of the contracts (this is exactly what happens when you register in the OnClickListener).
In your specific case if the Activity gets recreated while you're waiting for the launched contract to return (for example, configuration change happens or the system simply kills the app), the callback will be removed from the registry (the key remains there though), meaning that it will not be called with the results.
So, to summarize the whole thing: you can (should) safely register any contract as a member field in your Activity or in the onCreate(...), and you should never register a contract on-the-fly (a.k.a. conditionally). Registering the contract will do nothing special, the real deal happens when you launch it.
I want to create a Service that makes a network operation but i want it to run as long as an activity is open. So i want to bind it in the activity's lifecycle. If the user navigates to another activity and back i want it to restart. If the screen goes off and the user reopens it i want it to start again if its not possible to keep it
class PushService: Service() {
override fun onBind(intent: Intent?): IBinder? {
return null
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
// ToDo Create the request that i want
}
}
So i have to start and stop the service in the onResume and onStop of the Activity?
override fun onResume() {
super.onResume()
Intent(this, PushService::class.java).also { intent ->
startService(intent)
}
}
override fun onStop() {
super.onStop()
stopService(Intent(this, PushService::class.java))
}
Im not sure how to do that. Does anybody know the correct way?
Perhaps it would be a good idea to just create the proccess that i want inside the ViewModel instead of start a Service for it?
You are mostly doing it correctly, except you should either be using onResume/onPause or onStart/onStop, not mixing the two pairs. onStart and onStop are only called when your activity is going out of view entirely. So in your example, if a dialog from another app appeared in front of yours, onStop would not get called, but onResume would get called so your already started service will get multiple onStartCommand calls.
However, the whole point of Services is to run operations that continue when your app is not visible. If you're not doing that, it would be simpler to write your own class (maybe that implements LifecycleObserver or borrows lifecycleScope from the Activity) to handle the background work. Then you wouldn't have to deal with registering it in the manifest and handling intents.
Example of a LifecycleObserver:
// lifecycle is a property of AppCompatActivity. You can instantiate this class
// from your activity.onCreate()
class MyNeworkTaskManager(lifecycle: Lifecycle): LifecycleObserver, CoroutineScope by lifecycle.coroutineScope {
init {
lifecycle.addObserver(this)
}
#OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
private fun onResume() {
startMyRequest()
}
#OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
private fun onPause() {
pauseOrCancelMyRequest()
}
// Alternatively, if you want to expose suspend functions so your activity can request
// and respond to data in a coroutine without callbacks:
suspend fun getMyData(args: String): MyData {
val results = someNetworkRequestSuspendFunction(args)
return MyData(results)
}
// Or if you want to use coroutines for your network request, but still want
// your activity to use callbacks so it doesn't have to use coroutines to call
// these functions:
fun getMyDataAsync(args: String, callback: (MyData) -> Unit) = launch {
val results = someNetworkRequestSuspendFunction(args)
callback(MyData(results))
}
}
I don't do much with networking myself. But whatever library you're using, you can usually convert callbacks to coroutines using suspendCancellableCoroutine. There are tutorials for that you can look up.
Here is my method I am calling updateProgress() in Fragment onCreate()
and after navigating forward to another Activity or Fragment this updateProgress is still continue to work. How can I stop this ?
I was expecting if I am navigating to another Activity or Fragment
ViewModel onCleared() should be called and it will stop this update
private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob)
fun updateProgress() {
uiScope.launch {
delay(UPDATE_PROGRESS_INTERVAL)
updateCurrentProgramProgress()
contentRowsMutableData.postValue(contentRows)
updateProgress()
}
}
override fun onCleared() {
super.onCleared()
viewModelJob.cancel()
}
The onCleared is called when the fragment is destroyed. If you move to another fragment, the previous one will remain in the backstack (if you used addToBackstack) and thus is paused, but not destroyed.
If you want to stop processing data when the fragment is paused, you can have your ViewModel implement the LifecycleObserver interface, then you would add
#OnLifecycleEvent(Lifecycle.Event.ON_STOP)
fun stop() {
// add code here to stop work
}
You can then also resume processing with this
#OnLifecycleEvent(Lifecycle.Event.ON_START)
fun start() {
// code here
}
However, note that working while the fragment is paused may be desirable, so when the user returns to the fragment the work is complete and data can be shown immediately. It all depends on your specific circumstances.
I'm trying to create an app which will use MVVM architecture and there's one thing I quite don't understand.
Official Android docs say that's not a good idea to reference activity context in ViewModel's (as ViewModel may outlive activity) so I've started to wonder about usecase when I want to execute some action when my activity is resumed.
I know ViewModel's shouldn't do business logic themselves but even if I use some service class (let's say GPSService which has to start and pauseeach time activity is resumed on paused), and inside this service I react to activity onResume (using Lifecycle observer) I will still reference this activity from ViewModel as I'm referencing service which holds reference to activity being observed, this may cause activity leak (correct me if I'm wrong).
So my question is, how to react to activity or fragment lifecycle in MVVM architecture?
If you need to have a ViewModel be lifecycle aware, then you can have it implement LifeCycleObserver and override life cycle events as necessary. Example,
public class MyModel extends ViewModel implements
LifecycleObserver {
#OnLifecycleEvent(Lifecycle.Event.ON_STOP)
protected void onLifeCycleStop() {
// do something
}
}
In the activity or fragment then you can add the view model to the activity life cycle owner.
public class MyActivity extends AppCompatActivity {
protected MyModel mMyModel;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mMyModel = ViewModelProviders
.of(this)
.get(MyModel.class);
getLifecycle().addObserver(mMyModel);
}
}
I know ViewModel's shouldn't do business logic themselves
Yes, you're right. ViewModel should not contain business logic but
it should contain UI related logic. So basically, API calls or Some
location related stuffs should be avoided in ViewModel logic.
So what if you wanna make some scenario which can react to any activity lifecycle? I'll suggest you to use LifecycleObserver.
Why?, Because LifecycleObserver will provide you callbacks once it's LifecycleOwner will change it's state.
What is LifecycleOwner here? In our case it may be Activity/Fragment.
So, how you can achieve this?
Let's say you want to make location requests during resume & pause period of any activity.
So, for that you can create a class called LocationUpdates as LifecycleObserver like below:
class LocationUpdates : LifecycleObserver {
constructor(){
// some basic location related initialization here
}
#OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
fun connectListener() {
// this method will respond to resume event of our Lifecycle owner (activity/fragment in our case)
// So let's get location here and provide callback
}
#OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
fun disconnectListener() {
// this method will respond to pause event of our Lifecycle owner (activity/fragment in our case)
// So let's stop receiveing location updates here and remove callback
}
#OnLifecycleEvent(Lifecycle.Event.ON_DESTROY) // Optional if you want to cleanup references
fun cleanUp() {
// this method will respond to destroy event of our Lifecycle owner (activity/fragment in our case)
// Clean up code here
}
}
Now from your activity, you can directly make your LocationUpdates, and receive callback.
class MyActivity : AppCompatActivity() {
private lateinit var mLocationUpdates: LocationUpdates
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//Initialize your LifecycleObserver here & assign it to this activity's lifecycle
lifecycle.addObserver(mLocationUpdates)
}
}
You can refer to how to handle Lifecycle & Codelabs example.
Edit:
If you want to have ViewModel for that job, consider this:
class MyViewModel : ViewModel {
private lateinit var mLocationUpdates: LocationUpdates
constructor() : super() {
// initialize LocationUpdates here
}
// Assign our LifecyclerObserver to LifecycleOwner
fun addLocationUpdates(lifecycle: Lifecycle){
lifecycle.addObserver(mLocationUpdates)
}
//Optional, we really don't need this.
fun removeLocationUpdates(lifecycle: Lifecycle){
lifecycle.removeObserver(mLocationUpdates)
}
}
If your LocationUpdates depends upon Context, consider using AndroidViewModel.
We can now observe our location updates # any activity/fragment using LiveData, and assign our LifecycleObserver like below:
class MyActivity : AppCompatActivity() {
private val viewModel: MyViewModel by lazy {
return#lazy ViewModelProviders.of(this#MyActivity).get(MyViewModel::class.java)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel.addLocationUpdates(lifecycle)
}
}
Please note: there's still lot to cover but making this answer as short as possible. So, if you're still confused about something related then please feel free to ask me in comment. I will edit my answer.
with java 8 LifecycleObserver has been deprecated. According to the [docs][1] it is not recommended to use this class as it uses reflection.
Rather the docs recommend using DefaultLifecycleObserver. To do that, extend your ViewModel class with DefaultLifecycleObserver like:
class MyViewModel : ViewModel(), DefaultLifecycleObserver {//implement default lifecycle observer
override fun onCreate(owner: LifecycleOwner) {//override lifecycle events
super.onCreate(owner)
}
override fun onStart(owner: LifecycleOwner) {
super.onStart(owner)
}
override fun onResume(owner: LifecycleOwner) {
super.onResume(owner)
}
override fun onPause(owner: LifecycleOwner) {
super.onPause(owner)
}
override fun onStop(owner: LifecycleOwner) {
super.onStop(owner)
}
override fun onDestroy(owner: LifecycleOwner) {
super.onDestroy(owner)
}
}
and get all the lifecycle event callbacks in your viewmodel by registering your viewmodel as lifecycle event observer in your view class (e.g. Activity class) like:
class MyActivity : AppCompatActivity() {
private val myViewModel: MyViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
...
lifecycle.addObserver(splashViewModel)//registering observer
...
}
}
its just and update to the answer by #farid_z with kotlin and new sdk.
[1]: https://developer.android.com/reference/androidx/lifecycle/LifecycleObserver