This one will require a bit of context, please bare with me...
I have migrated a dependency for evaluating NFC data to a new application. Whenever an NFC tag is discovered, my application will spawn an Activity to handle the event. In the onCreate function of this NfcActivity, a background service (let's call it MyNfcHelperService) is started to retrieve some data on the scanned tag:
class NfcActivity : AppCompatActivity() {
/*...*/
override fun onCreate(savedInstanceState: Bundle?) {
/*...*/
val intent = Intent(this, MyNfcHelperService::class.java)
.putExtra(/*...*/)
startService(intent)
}
}
The work produced by this service is later retrieved and used by the NfcActivity. It all used to work just fine, but once released into the wild we noticed some crashes, which would report Not allowed to start service Intent on the startService(intent) call.
I quickly came across this related post, suggesting this is due to some improvements in RAM management on background processes (introduced in Android 8).
Following the accepted answer and comments raised, I studied the docs on JobIntentServices and ended up with a similar setup. I would've liked to drop the MyNfcHelperService all together and move its logic into the MyJobIntentService. But what happens inside MyNfcHelperService is an absolute black-box to me. Thus, I wrapped the aforementioned service inside the onHandleWork of my derived JobIntentService like so:
class MyJobIntentService: JobIntentService() {
companion object {
private const val JOB_ID = 1000
fun start(context: Context) {
val intentPkm = Intent(context, MyNfcHelperService::class.java)
.putExtra(/*...*/)
enqueueWork(context, intentPkm)
}
private fun enqueueWork(context: Context, intent: Intent) {
enqueueWork(context, MyJobIntentService::class.java, JOB_ID, intent)
}
}
override fun onHandleWork(intent: Intent) {
applicationContext.startService(intent)
}
}
Then I applied this class in NfcActivity:
class NfcActivity : AppCompatActivity() {
/*...*/
override fun onCreate(savedInstanceState: Bundle?) {
/*...*/
MyJobIntentService.start(applicationContext)
}
}
Thus far, the code seems to work. But I am hesitant to release it into the wild, because it feels a bit hacky and I am unsure if this solution actually solved the aforementioned issue. After all, I understand that this infrastructure creates a background service from a scheduled job.
So, is my code robust towards the java.lang.IllegalStateException: Not allowed to start service Intent error, or did I totally head the wrong way? If the latter is the case, can anyone suggest an alternate approach, taking into account that I cannot access the guts of MyNfcHelperService?
After #MD's comment, I changed my approach towards using WorkManager. This should be most robust against different API versions. I followed the official docs and arrived at the following worker setup:
class MyNfcHelperServiceWorker(val context: Context, workerParams: WorkerParameters): Worker(context, workerParams) {
override fun doWork(): Result {
val intent = Intent(context, MyNfcHelperService::class.java)
.putExtra(/*...*/)
context.startService(intent)
return Result.success()
}
}
Then I adjusted the code inside NfcActivity like so:
class NfcActivity : AppCompatActivity() {
/*...*/
override fun onCreate(savedInstanceState: Bundle?) {
/*...*/
OneTimeWorkRequestBuilder<MyNfcHelperServiceWorker>()
.build()
.also { helperServiceWorkRequest ->
WorkManager.getInstance(this)
.enqueue(helperServiceWorkRequest)
}
}
}
Initial tests have worked just fine. My understanding of why this fixes the java.lang.IllegalStateException: Not allowed to start service Intent issue would be that android will now schedule a work request which meets the requirements of launching MyNfcHelperService only when my app is allowed to create background processes.
That said, I still have a bit of a headache using a worker to start a service. Feels really redundant to do so and I am unsure of any additional implications this may lead. Thus, I wont accept this as an answer just now.
I'd really appreciate any additional comments and/or answers on the matter!
Related
I want to schedule my MainActivty to perform checks on its data periodically without showing UI to the user. When a condition is true, I want to show a notification.
Furthermore, I need to check for this notification only once per day, without an exact timing and I want this check to remain scheduled even if the device is rebooted /after poweron.
Looks like the worker model with TimeUnit.ONE_DAY is the best fit for me (Here I am setting the interval at one minute so I don't need to wait for one day to test).
I create in "onCreate" method the Periodic Request Builder and the enqueue the request in the work manager.
Outside onCreate method I define the sendNotification method and provide my class implementatio on Notification Worker, where I define the action to be performed when the woker callback is fired.
In main class I have:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
[...]
val CheckRequest = PeriodicWorkRequestBuilder<NotificationWorker>(1, TimeUnit.MINUTES).build()
WorkManager.getInstance(context).enqueue(CheckRequest)
}
fun sendNotification(){
with(NotificationManagerCompat.from(this)) {
notify(notificationId, builder.build())
}
class NotificationWorker(appContext: Context, workerParams: WorkerParameters):
Worker(appContext, workerParams) {
override fun doWork(): Result {
// Do the work here--in this case, upload the images.
MainActivity().sendNotification()
// Indicate whether the work finished successfully with the Result
return Result.success()
}
}
}
My Issue is that I encounter the following error
java.util.concurrent.ExecutionException: java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
I think that what's happening is that I am calling a method from the super class of the worker when the UI is not active, so the app context etc is not availlable to the called method.
Any hin on this point would be gladly appreciated.
Kind regards,
So I am making a Flutter plugin and I am attempting to run Kotlin code on Android. The problem is, this code runs a method which attempts to start an activity without the FLAG_ACTIVITY_NEW_TASK flag on the intent. The problem with this is that it also does NOT have a way to give it an intent instance as it attempts to instantiate an instance inside the method itself. The method expects to be called from a button or other method that is stored on the UI and called from it. However, since it is called from the onMethodCall method in the Flutter plugin, it does not seem to work. I have attempted many workarounds such as adding a method inside the Activity and running the code inside while calling it from the flutter plugin class. I have also tried using the UIThread and no luck either. Any workarounds?
Note: I have not provided any code due to keeping this API hidden. It should only be known that I am running the code from the onMethodCall event.
Error: Calling startActivity() from outside of an Activity context requires the FLAG_ACTIVITY_NEW_TASK flag. Is this really what you want?
You can extend your plugin to implement ActivityAware in your plugin class, when you implement it, you get a couple of callbacks that gives you the current activity. Like this :
lateinit activity: Activity? = null
override fun onDetachedFromActivity() {
activity = null
}
override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) {
activity = binding.activity
}
override fun onAttachedToActivity(binding: ActivityPluginBinding) {
activity = binding.activity
}
override fun onDetachedFromActivityForConfigChanges() {
activity = null
}
After that you can just startActivity from the assigned activity variable.
Let me know if you need further help.
As you mentioned, For Flutter plugin any platform-dependent logics should be kept in the subclass of FlutterActivity which was used to show flutter module/screens inside a native module. Now you can launch intent from that subclass without any additional flags.
#note - Subclass of FlutterActvity should be kept in the native module.
class FlutterResponseActivity : FlutterActivity() {
private var methodResult: Result? = null
override fun provideFlutterEngine(context: Context): FlutterEngine? {
return MyApplication.mContext.flutterEngine //Pre-warmed flutter engine
}
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
GeneratedPluginRegistrant.registerWith(flutterEngine)
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call, result ->
when (call.method) {
"startMainActivity" -> {
startMainActivity()
result.success(true)
}
else -> result.notImplemented()
}
}
}
private fun startMainActivity() {
val intent = Intent(this, MainActivity::class.java)
startActivity(intent)
}
}
I'm trying to create an app that can broadcast Android views on the Chromecast, and I thought I found something promising in CastRemoteDisplayLocalService. I created a simple test app but found the callback onCreatePresentation was never called when I casted my device. After some searching I discovered it was because my application was not published as a Remote Display Application but a Custom Application Receiver from the Google Cast Developer Console.
Unfortunately when I try to create a new application from the console, Remote Display Application is not an option. After some searching, I came across this Stack Overflow question that said Remote Display API is now deprecated. There is an interface called CastRemoteDisplayApi which is marked as deprecated, but the classes I have been trying to use are not marked as such.
This leads me to wondering if CastRemoteDisplayLocalService and all other Remote Display classes not marked as deprecated are in fact deprecated and unusable, or if perhaps the functionality was shifted to work in a Custom Receiver by configuring it to accept remote displays.
This is what the relevant code looks like right now:
MainActivity.kt
private fun startCastService() {
val intent = Intent(this, MainActivity::class.java)
val pendingIntent = PendingIntent.getActivity(this, 0, intent, 0)
val notificationSettings = CastRemoteDisplayLocalService.NotificationSettings.Builder().setNotificationPendingIntent(pendingIntent).build()
CastRemoteDisplayLocalService.startService(this, CastRemoteDisplayLocalServiceImpl::class.java, "2839EC8D", castDevice, notificationSettings, object : CastRemoteDisplayLocalService.Callbacks {
override fun onRemoteDisplaySessionEnded(p0: CastRemoteDisplayLocalService?) {
Log.d(TAG, "onRemoteDisplaySessionEnded")
}
override fun onRemoteDisplaySessionError(p0: Status?) {
Log.d(TAG, "onRemoteDisplaySessionError")
}
override fun onRemoteDisplaySessionStarted(p0: CastRemoteDisplayLocalService?) {
Log.d(TAG, "onRemoteDisplaySessionStarted")
}
override fun onServiceCreated(p0: CastRemoteDisplayLocalService?) {
Log.d(TAG, "onServiceCreated")
}
})
}
CastRemoteDisplayLocalServiceImpl.kt
class CastRemoteDisplayLocalServiceImpl : CastRemoteDisplayLocalService() {
val TAG = "CastRemoteDisplayLoc..."
// This function gets called
override fun onCreate() {
super.onCreate()
Log.d(TAG, "onCreate")
}
// This function does not get called
override fun onCreatePresentation(p0: Display?) {
Log.d(TAG, "onCreatePresentation")
}
override fun onDismissPresentation() {
Log.d(TAG, "onDismissPresentation")
}
}
If there's a way with the Custom Application Receiver to get the onCreatePresentation callback that would solve this, I'm having difficulty finding it. If CastRemoteDisplayLocalService is in fact deprecated, is there another way to easily cast Android views to a Chromecast? Thanks!
I implemented an AlarmManager to send notifications when user adds a due date to a Task. However, when the user turns off the device, all the alarms are lost. Now I'm updating the BroadcastReceiver to receive an android.intent.action.BOOT_COMPLETED and reschedule all the alarms set to each task.
My first attempt was to get an Rx Single with all the tasks where the due date is higher than the current time inside the BroadcastReceiver, then reschedule all the alarms. The issue is I'm not able to dispose the Observable once the BroadcastReceiver has no lifecycle. Also, it seems that this is not a good approach.
During my researches, the IntentService was a good solution for this case, but I'm getting into the new WorkManager library and the OneTimeWorkRequest looks like a good and simple solution.
The Worker is being called and executing correctly, but I'm not able to dispose the Observable because the onStopped method is never called.
Here is the implementation, based on this snippet:
class TaskAlarmWorker(context: Context, params: WorkerParameters) :
Worker(context, params), KoinComponent {
private val daoRepository: DaoRepository by inject()
private val compositeDisposable = CompositeDisposable()
override fun doWork(): Result {
Timber.d("doWork")
val result = LinkedBlockingQueue<Result>()
val disposable =
daoRepository.getTaskDao().getAllTasks().applySchedulers().subscribe(
{ result.put(Result.SUCCESS) },
{ result.put(Result.FAILURE) }
)
compositeDisposable.add(disposable)
return try {
result.take()
} catch (e: InterruptedException) {
Result.RETRY
}
}
override fun onStopped(cancelled: Boolean) {
Timber.d("onStopped")
compositeDisposable.clear()
}
}
Is WorkManager a good solution for this case?
Is it possible to dispose the Observable correctly?
Yes WorkManager is a good solution(even could be the best one)
You should use RxWorker instead of Worker. here is an example:
To implement it. add androidx.work:work-rxjava2:$work_version to your build.gradle file as dependency.
Extend your class from RxWorker class, then override createWork() function.
class TaskAlarmWorker(context: Context, params: WorkerParameters) :
RxWorker(context, params), KoinComponent {
private val daoRepository: DaoRepository by inject()
override fun createWork(): Single<Result> {
Timber.d("doRxWork")
return daoRepository.getTaskDao().getAllTasks()
.doOnSuccess { /* process result somehow */ }
.map { Result.success() }
.onErrorReturn { Result.failure() }
}
}
Important notes about RxWorker:
The createWork() method is called on the main thread but returned single is subscribed on the background thread.
You don’t need to worry about disposing the Observer since RxWorker will dispose it automatically when the work stops.
Both returning Single with the value Result.failure() and single with an error will cause the worker to enter the failed state.
You can override onStopped function to do more.
Read more :
How to use WorkManager with RxJava
Stackoverflow answer
You can clear it in onStoped() method then compositeDisposable.dispose();
Then call super.onStoped()
In the below shown diagram, I am having 3 modules(as android library) which extends the base "common components module" and all this 3 modules will be added to a single android application. All 3 modules are independent modules but when it comes as an application, it would require to share some data, launch other module and requires more inter-communication.
So can anyone let me know how we can implement the "Data Sharing Layer" and "Navigation Controller" in this kind of architecture?
Example: Module1 -> Login, Module2 -> Profile Management etc and there could be "n" number of modules based on the application need.
What you are looking for is basically a clean approach on how to communicate with other classes. There is not really a difference in whether or not they are in different modules.
The following sample describes how a LoginActivity could navigate to some profile activity. This is just a basic sample to be improved with what you actually need and intend to do!
Define your interfaces
Write interfaces of what you need. Your Login should be able to open a profile page? Well this sounds like it needs a LoginNavigator!
interface LoginNavigator {
void showProfile();
}
Include those interfaces in your shared components. There is not really a possibility to go without defining interfaces. You can make them more abstract or more fine grained, this is entirely up to you.
Declare your dependencies
Remember how your Login needs a LoginNavigator? The real problem is on how to supply it to your class. You should have a look at dependency injection, since there are frameworks liks dagger-2 that (could) make this easier. For now, we define an interface for a common component, so that we can retrieve the dependencies we need.
interface NavigatorProvider {
LoginNavigator provideNavigator();
}
You may guess it—this method is used to get the actual LoginNavigator that you can use to get the implementation of that interface. Usually you would just declare this dependency in the constructor, but since android is somewhat special you need to get it from somewhere yourself.
Provide your dependencies
The easiest way to go is to just have your application implement this interface (or hold an object that does).
class MyApp extends Application implements NavigatorProvider {
LoginNavigator provideNavigator() {
return new LoginNavigator() {
void showProfile() {
// just some sample code. You should probably not use an
// anonymous class
startActivity(new Intent(this, MyProfileActivity.class));
}
};
}
}
Again, you could also return an object that is implementing this interface. This is just a basic sample.
Use the interface. (And don't care about the implementation)
Now the dependency injection is nearly complete. We have an interface that we need, we have some way to provide the dependency, all that's left is to get it and use it.
class LoginActivity extends Activity {
LoginNavigator mNavigator;
void onCreate() {
// get the dependency
mNavigator = ((NavigatorProvider) getApplicationContext()).provideNavigator();
// use it where needed. (again, just sample code)
findShowProfileView().setOnClickListener(new OnClickListener() {
void onClick(View view) {
mNavigator.showProfile();
}
});
}
}
Now the dependency is provided, and ready to be used.
What this sample shows is how to basically use interfaces to decouple logic. You will still need some point of entry, since android does not allow to implement your own constructors—this is why the application class is used.
I found that solution using Local Broadcast which is implemented in Application Class and send event on Local Broadcast which is received in Application Class.
class AppApplication : Application() {
override fun onCreate() {
super.onCreate()
registerBroadcast()
}
private fun startProfileActivity() {
val intent = newIntent<MyProfileActivity>(this)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
this.startActivity(intent)
}
private fun registerBroadcast() {
LocalBroadcastManager.getInstance(this)
.registerReceiver(broadCastReceiver,IntentFilter(BROADCAST_VIEW_PROFILE))
}
private fun unregisterBroadcast() {
LocalBroadcastManager.getInstance(this)
.unregisterReceiver(broadCastReceiver)
}
private val broadCastReceiver = object : BroadcastReceiver() {
override fun onReceive(contxt: Context?, intent: Intent?) {
when (intent?.action) {
BROADCAST_VIEW_PROFILE -> {
startProfileActivity()
}
}
}
}
override fun onTerminate() {
super.onTerminate()
unregisterBroadcast()
}
}
When you send the event in an Application like this
private fun viewProfileEventSend() {
// Send Broadcast for view profile to `APP`
val intent = Intent(BROADCAST_VIEW_PROFILE)
LocalBroadcastManager.getInstance(requireContext()).sendBroadcast(intent)
}
Because your module doesn't need to get the instance of Application or any interface.