I am trying to build a Flutter app that does the following:
1) Run an alarm manager every minute (even when app is in background/closed).
2) When the alarm manager's onReceive method is called, get the users location.
3) Store this location in a SQL/SQF database.
Basically, I have all the code working. However, I'd prefer to do step 2 and 3 with two different plugins, as to create more modularity. But to do so, I need to have an instance of a FlutterView, such that I can do a "MethodChannel(flutterView, CHANNEL).invokeMethod(methods, args);" from Android.
(In android/Java, onReceive method) I have tried to get the FlutterView from the context, like so:
private FlutterView viewFromAppContext(Context context) {
Application app = (Application) context.getApplicationContext();
if (!(app instanceof FlutterApplication)) {
Log.i(TAG, "viewFromAppContext app not a FlutterApplication");
return null;
}
FlutterApplication flutterApp = (FlutterApplication) app;
Activity activity = flutterApp.getCurrentActivity();
if (activity == null) {
Log.i(TAG, "viewFromAppContext activity is null");
return null;
}
if (!(activity instanceof FlutterActivity)) {
Log.i(TAG, "viewFromAppContext activity is not a FlutterActivity");
return null;
}
FlutterActivity flutterActivity = (FlutterActivity) activity;
return flutterActivity.getFlutterView();
}
However, when I try to do this when the app is in the background, the activity is null.
Is it possible to create a new activity and/or flutterview in this scenario (which can direct to my 'setMethodCallHandler' method in dart)?
I think what you are looking for is a FlutterNativeView called with the second param set to true; it is not easy to follow, slightly divergent from the linked git repo and is engineered well in excess of your needs, but Ben Konyi has a working example of this. I think you might enjoy checking out some of the ways he kept the other plugins working and the service working, if not the methodchannel impl (with three callback domains).
Related
I have a WebView in the layout xml of my MainActivity, to which I setWebViewClient(new WebViewClient()), followed by loadUrl(...) in onCreate.
Most of the time the app runs fine and the Web content is displayed correctly.
But in some cases, opening the app causes a crash. I've noticed that it happens when the app scheduled a PendingIntent broadcast with AlarmManager, which triggers a Notification whose contentIntent is a PendingIntent.getActivity set to launch MainActivity.
But it happens only in the case when the user has removed the app from the stack of active apps in the meantime (Notification is visible, not yet clicked, and stack if apps cleared. So, app process probably stopped?).
Seemingly no other system modifications in between (in particular no app/system update, no playing around with user profiles or Chrome app.)
Stack trace:
java.lang.RuntimeException:
at android.webkit.WebViewDelegate.getPackageId (WebViewDelegate.java:164)
at yj.a (PG:16)
at xH.run (PG:14)
at java.lang.Thread.run (Thread.java:764)
Occurs with Android 7.0 thru 9. Also, seems to have started to occur when I upgraded target SDK to 28.
I don't use explicitly a WebViewDelegate. It must be internal system code (hence the obfuscation).
By reading the source code of AOSP, it seems that the WebView fails to retrieve the package to which it belongs -- but why sometimes only!?
Any help appreciated! Thanks.
It has taken weeks of investigation on and off, but I've finally found why I'm seeing this issue. For me, it was just because I'd overridden the getResources() method in my application scope to use the current activity. Something like this:
public class MyApplication extends MultiDexApplication {
private static MyApplication sInstance = null;
private WeakReference<Activity> mCurrentActivity;
public static MyApplication getInstance() {
return sInstance;
}
public void setCurrentActivity(Activity activity) {
mCurrentActivity = new WeakReference<>(activity);
}
public Activity getCurrentActivity() {
return mCurrentActivity == null ? null : mCurrentActivity.get();
}
#Override
public Resources getResources() {
// This is a very BAD thing to do
Activity activity = getCurrentActivity();
if (activity != null) {
return activity.getResources();
}
return super.getResources();
}
}
This was done as a shortcut as I often wanted to get strings that were activity-specific, so I was calling MyApplication.getInstance().getResources().getString(). I now know this was a bad thing to do - removing my override of this method instantly fixed it.
So the key takeaway from this for me is that when the WebView is initialising, it MUST be able to get hold of the application context, so that the resources passed into WebViewDelegate.getPackageId() are at the application level - the activity context isn't enough, and causes this error.
As a side note - I wasn't even trying to add a WebView to my application. I was only actually using the following:
String userAgent = WebSettings.getDefaultUserAgent(this);
I was then passing this value into a custom media player that I'm using. Passing "this" as either application or activity scope always failed, due to my override.
Looking through documentation,you can see that error is thrown when package can't be found.Check your syntax ,package name and try again.
https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/webkit/WebViewDelegate.java (Line 164)
/**
* Returns the package id of the given {#code packageName}.
*/
public int getPackageId(Resources resources, String packageName) {
SparseArray<String> packageIdentifiers =
resources.getAssets().getAssignedPackageIdentifiers();
for (int i = 0; i < packageIdentifiers.size(); i++) {
final String name = packageIdentifiers.valueAt(i);
if (packageName.equals(name)) {
return packageIdentifiers.keyAt(i);
}
}
throw new RuntimeException("Package not found: " + packageName);
}
I'm writing UI tests with Espresso. App cooperates tightly with server, so in many cases, I need to wait for either value to be calculated, or data is got and displayed, etc. Espresso suggests using IdlingResource for this.
My IdlingResource classes look like this (simple and clear example).
public class IRViewVisible implements IdlingResource {
private View view;
private ResourceCallback callback;
public IRViewVisible(View view) {
this.view = view;
}
#Override
public String getName() {
return IRViewVisible.class.getName();
}
#Override
public boolean isIdleNow() {
if(view.getVisibility() == View.VISIBLE && callback != null) {
callback.onTransitionToIdle();
return true;
}
return false;
}
#Override
public void registerIdleTransitionCallback(ResourceCallback resourceCallback) {
this.callback = resourceCallback;
}
}
Please correct me if I'm wrong anywhere (as sometimes it seems to me that my IdlingResources do not work properly).
I register the idling resource in setUp() like this:
IRViewVisible ir = new IRViewVisible(View v);
Espresso.registerIdlingResources(ir).
Unregister it on tearDown().
I found this article (there is a section called "Register a component tied to an Activity instance") — I do not use his schema, but I checked hashcode of view that was set to IdlingResource after registering (in each method), and it's not the same view — all hashes are different.
Another question: One Test class (it's results) can't have any effect on another Test class, can it?
I'm guessing your problem stems from getName() returning the same name for all instances of IRViewVisible. This means you can only have one registered instance of it at a time - any subsequent registrations will fail (silently!).
You mention that you clear the IdlingResources at the end of each test, but if you are register multiple instances of it at once, you need to make sure each instance has a unique name. it's not clear from your question if you're registering multiple instances of IRViewVisible in a single test.
As to your final question: Yes, it is possible. Android doesn't completely shut down the Application between test runs - just the Activities. Common things which can cause problems:
Failing to clear persistent state (saved data).
Failing to clear global state - e.g. static variables/singletons
Not waiting for background threads to finish running.
As an aside, it's worth noting that you only call onTransitionToIdle() inside isIdleNow(). This works (thanks #Be_Negative for the heads up!) but it could slow down your tests a lot, since Espresso will only poll isIdleNow() every few seconds. If you call onTransitionToIdle() as soon as the view becomes visible, it should speed things up considerably.
I needed something similar to your IRViewVisible myself, here's my effort.
So the isIdleNow() method will never return true if you don't set a callback to the idlingResource?
I reckon it's better to refactor it like this:
#Override
public boolean isIdleNow() {
boolean idle = view.getVisibility() == View.VISIBLE;
if(idle && callback != null) {
callback.onTransitionToIdle();
}
return idle;
}
Well, first of all you shouldn't need to use Espresso IdlingResource to test server calls. If you use AsyncTasks in your server calls, Espresso will be able to know when to be idle and when not. If this is not enough: try to refactor your code in this way:
IRViewVisible idlingResource = new IRViewVisible(yourView);
IdlingPolicies.setMasterPolicyTimeout(waitingTime * 2, TimeUnit.MILLISECONDS);
IdlingPolicies.setIdlingResourceTimeout(waitingTime * 2, TimeUnit.MILLISECONDS);
// Now we wait
Espresso.registerIdlingResources(idlingResource);
// Stop and verify
// Clean up
Espresso.unregisterIdlingResources(idlingResource);
Hope to be helpful.
I have implemented a bus for my app for communication between fragments and activities. I add a subscriber by adding an instance of either a Fragment or an Activity to a list. and I iterate through that list invoking a method to notify each of the subscribers of what is going on. Now I need to keep the list clean, I don't want to add multiple instances of of the same class in the list. I can use equals() for an Activity but I cant for a Fragment because its final so I cant override it.
WHAT I HAVE TRIED
I have tried to keep a Class object of each subscriber in the list which works fine until I go to invoke the method. You cant invoke a method without an instance to invoke it from. So that doesnt work.
I could also keep a separate list, one to hold Class objects and one to hold the actual instance. But I want to avoid adding another dependency if at all possible.
I could also manually do a instanceof check for each Fragment, but I dont want to do that because I already have 5 fragments, and if I add or remove any then I have to come back here and update this method.
So my question is, other than adding another List to hold the Class objects or manual instanceof checks, are there any other ways I can make sure I dont add multiple instances to the subscribers List?
Here is the relevant code if you need it:
public void subscribe(Object object) {
if (!mSubscribers.contains(object)) {
mSubscribers.add(object);
}
}
public void notifySubscribers(BusEvent event) throws InvocationTargetException, IllegalAccessException {
for (Object o : mSubscribers) {
Method methodToCall = getMethodToCall(o);
if (methodToCall != null) {
methodToCall.invoke(o, event);
}
}
}
Ok I have found a suitable answer to my problem. I want to share it here in hopes that it will help someone else out. Android has a class called LocalBroadcastManager. It is available in the v4 support library. In your activity you call 'LocalBroadcastManager.getInstance().registerReceiver()'. You pass into that method a class that extends BroadcastReceiver and an 'IntentFilter' to tell the receiver what to listen for. Then in any class including Fragments you call LocalBroadcastManager.getInstance().sendBroadcast() and pass in an Intent that matches the IntentFilter you used when registering. Here is the code I used to get it to work:
private void registerLocalBroadcastReceiver() {
// call this method in your activity (or any class you want to listen for broadcasts)
LocalBroadcastManager manager = LocalBroadcastManager.getInstance(this);
manager.registerReceiver(new OpenMenuBroadcastReceiver(), new IntentFilter("open-html"));
}
private void sendMessageToActivity(int position) {
// use this in a fragment (or any other class) to send a message
LocalBroadcastManager broadcast = LocalBroadcastManager.getInstance(getActivity());
Intent message = new Intent("open-html");
String name = (String) getListAdapter().getItem(position);
message.putExtra("name", name);
broadcast.sendBroadcast(message);
}
class OpenMenuBroadcastReceiver extends BroadcastReceiver {
// this is an inner class to my activity, when you send the message this method
// will be called to handle the message
#Override
public void onReceive(Context context, Intent intent) {
String name = intent.getStringExtra("name");
if (name != null && name.equalsIgnoreCase("home")) {
replaceFragment(Tag.HOME_FRAGMENT.getTag(), new HomeFragment(), R.id.main_frame);
mDrawerLayout.closeDrawer(Gravity.START);
return;
}
openMenuItemsFragment(name);
}
}
The good thing about this is that it is completely local to your app. External apps cant receive your broadcasts so its secure. You can find out more on how to use it on the Android developer site.
I've written an iOS app that I want to port to android. This app is a front-end for a web-based timekeeping system at work. Unfortunately, the system doesn't have an API, so I'm screen-scraping it using xpath queries in javascript. In iOS, I only have to load this page once because I get full control over when instances of my UIWebView get destroyed.
The iOS app only has 3 use cases, each which break into separate Android
activities:
Login
View a list of all reported times
Report a new time.
Using a naive approach, my android views (including the WebView I need to use to interact with the timekeeping system) will be destroyed and recreated when I switch between views. If I have to reload the WebView every time I switch activities, my app will appear very slow.
Is it possible to share a WebView instance between multiple
activities? I know I can set the launchMode to singleInstance, but ideally, it would be nice to allow separate instances so that the back button would function normally.
Per a suggestion on from Eric Burke at STL Mobile Dev:
See if your Android Application can create the WebView. You won't actually display the WebView anywhere, it's just created for interaction with the web site. The WebView instance's lifecycle is then managed by your Application rather than an Activity.
I was able to share my WebView between my Activity instances. I specified a custom Application in my manifest, and created the WebView inside the Application. The Application is a Context and it lives as long as the app does, so all Activity objects can share the WebView that way.
The way I solved this problem is little different. It pivots around couple of things.
WebViews can be created with application context.
There is something called MutableContextWrapper, we can flip the underlying context inside it.
So, we create the web view in the pool with mutable context wrapper with appContext and when an activity context asks for it, we flip the context and return it. We flip the context to appContext when we return the webView back.
Something like this
// Has a single web view, and crashes if a request to obtain comes in and the
// cached instance is already allocated.
public class SingularWebViewPool implements WebViewPool {
private WebView mCachedInstance;
private volatile int mBorrower;
private final int mSignature;
private final Context mAppCtx;
public SingularWebViewPool(Context appCtx) {
this.mAppCtx = appCtx;
this.mCachedInstance = new WebView(appCtx);
// We will match this every time, we flip to app context to ensure a different web view is
// not handed over to us.
this.mSignature = mCachedInstance.hashCode();
}
#Override
#NonNull
public WebView obtain(#NonNull Context activity) {
if (mCachedInstance != null) {
Context ctx = mCachedInstance.getContext();
if (ctx instanceof MutableContextWrapper) {
((MutableContextWrapper) ctx).setBaseContext(activity);
} else
// We should not reach here!
throw new IllegalStateException("Cached web view stored without a mutable context wrapper.");
WebView temp = mCachedInstance;
mCachedInstance = null;
this.mBorrower = activity.hashCode();
return temp;
} else
throw new IllegalStateException("Pool not having a cached web view instance when obtain() was called.");
}
#Override
public boolean release(#NonNull WebView webView, #NonNull Context borrower) {
// Validate the last borrower.
if (borrower.hashCode() != this.mBorrower) {
return false;
}
Context ctx = webView.getContext();
if (ctx instanceof MutableContextWrapper) {
((MutableContextWrapper) ctx).setBaseContext(mAppCtx);
} else
throw new IllegalStateException("Cached web view stored without a mutable context wrapper.");
// match the signature.
if (mSignature != mCachedInstance.hashCode()) {
throw new IllegalStateException("A different web view is released other than what we have given out.");
}
mCachedInstance = webView;
mBorrower = 0;
return true;
}
}
I was pretty excited to see how easy it is to set up Google Analytics with my app, but the lack of documentation has me sitting with a few questions. The only information that I can find is right from the documentation here, which only looks at reporting PageViews and Events from one Activity. I want to report PageViews and Events across multiple Activities in my app.
Right now in the onCreate() of all of my activities, I am calling:
tracker = GoogleAnalyticsTracker.getInstance();
tracker.start("UA-xxxxxxxxx", this);
And in the onDestroy() of all of my activities:
tracker.stop();
I then track PageViews and Events as needed, and Dispatch them along with another HTTP request I am performing. But I'm not so sure this is the best way. Should I be calling start() and stop() in each activity, or should I only call start() and stop() in my main launcher activity?
The problem with calling start()/stop() in every activity (as suggested by Christian) is that it results in a new "visit" for every activity your user navigates to. If this is okay for your usage, then that's fine, however, it's not the way most people expect visits to work. For example, this would make comparing android numbers to web or iphone numbers very difficult, since a "visit" on the web and iphone maps to a session, not a page/activity.
The problem with calling start()/stop() in your Application is that it results in unexpectedly long visits, since Android makes no guarantees to terminate the application after your last activity closes. In addition, if your app does anything with notifications or services, these background tasks can start up your app and result in "phantom" visits. UPDATE: stefano properly points out that onTerminate() is never called on a real device, so there's no obvious place to put the call to stop().
The problem with calling start()/stop() in a single "main" activity (as suggested by Aurora) is that there's no guarantee that the activity will stick around for the duration that your user is using your app. If the "main" activity is destroyed (say to free up memory), your subsequent attempts to write events to GA in other activities will fail because the session has been stopped.
In addition, there's a bug in Google Analytics up through at least version 1.2 that causes it to keep a strong reference to the context you pass in to start(), preventing it from ever getting garbage collected after its destroyed. Depending on the size of your context, this can be a sizable memory leak.
The memory leak is easy enough to fix, it can be solved by calling start() using the Application instead of the activity instance itself. The docs should probably be updated to reflect this.
eg. from inside your Activity:
// Start the tracker in manual dispatch mode...
tracker.start("UA-YOUR-ACCOUNT-HERE", getApplication() );
instead of
// Start the tracker in manual dispatch mode...
tracker.start("UA-YOUR-ACCOUNT-HERE", this ); // BAD
Regarding when to call start()/stop(), you can implement a sort of manual reference counting, incrementing a count for each call to Activity.onCreate() and decrementing for each onDestroy(), then calling GoogleAnalyticsTracker.stop() when the count reaches zero.
The new EasyTracker library from Google will take care of this for you.
Alternately, if you can't subclass the EasyTracker activities, you can implement this manually yourself in your own activity base class:
public abstract class GoogleAnalyticsActivity extends Activity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Need to do this for every activity that uses google analytics
GoogleAnalyticsSessionManager.getInstance(getApplication()).incrementActivityCount();
}
#Override
protected void onResume() {
super.onResume();
// Example of how to track a pageview event
GoogleAnalyticsTracker.getInstance().trackPageView(getClass().getSimpleName());
}
#Override
protected void onDestroy() {
super.onDestroy();
// Purge analytics so they don't hold references to this activity
GoogleAnalyticsTracker.getInstance().dispatch();
// Need to do this for every activity that uses google analytics
GoogleAnalyticsSessionManager.getInstance().decrementActivityCount();
}
}
public class GoogleAnalyticsSessionManager {
protected static GoogleAnalyticsSessionManager INSTANCE;
protected int activityCount = 0;
protected Integer dispatchIntervalSecs;
protected String apiKey;
protected Context context;
/**
* NOTE: you should use your Application context, not your Activity context, in order to avoid memory leaks.
*/
protected GoogleAnalyticsSessionManager( String apiKey, Application context ) {
this.apiKey = apiKey;
this.context = context;
}
/**
* NOTE: you should use your Application context, not your Activity context, in order to avoid memory leaks.
*/
protected GoogleAnalyticsSessionManager( String apiKey, int dispatchIntervalSecs, Application context ) {
this.apiKey = apiKey;
this.dispatchIntervalSecs = dispatchIntervalSecs;
this.context = context;
}
/**
* This should be called once in onCreate() for each of your activities that use GoogleAnalytics.
* These methods are not synchronized and don't generally need to be, so if you want to do anything
* unusual you should synchronize them yourself.
*/
public void incrementActivityCount() {
if( activityCount==0 )
if( dispatchIntervalSecs==null )
GoogleAnalyticsTracker.getInstance().start(apiKey,context);
else
GoogleAnalyticsTracker.getInstance().start(apiKey,dispatchIntervalSecs,context);
++activityCount;
}
/**
* This should be called once in onDestrkg() for each of your activities that use GoogleAnalytics.
* These methods are not synchronized and don't generally need to be, so if you want to do anything
* unusual you should synchronize them yourself.
*/
public void decrementActivityCount() {
activityCount = Math.max(activityCount-1, 0);
if( activityCount==0 )
GoogleAnalyticsTracker.getInstance().stop();
}
/**
* Get or create an instance of GoogleAnalyticsSessionManager
*/
public static GoogleAnalyticsSessionManager getInstance( Application application ) {
if( INSTANCE == null )
INSTANCE = new GoogleAnalyticsSessionManager( ... ,application);
return INSTANCE;
}
/**
* Only call this if you're sure an instance has been previously created using #getInstance(Application)
*/
public static GoogleAnalyticsSessionManager getInstance() {
return INSTANCE;
}
}
The SDK now has a external library which takes care of all of this. Its called EasyTracker. You can just import it and extend the provided Activity or ListActivity, create a string resource with your code and you are done.
The tracker will only track the activity where it's executed. So, why don't you subclass an Activity which start it every time on onCreate:
public class GAnalyticsActivity extends Activity{
public void onCreate(Bundle icicle){
super.onCreate(icile);
tracker = GoogleAnalyticsTracker.getInstance();
tracker.start("UA-xxxxxxxxx", this);
}
// same for on destroy
}
Then, you extends that class for every activity you use:
public class YourActivity extends GAnalyticsActivity{
public void onCreate(Bundle icicle){
super.onCreate(icile);
// whatever you do here you can be sure
// that the tracker has already been started
}
}
The approach I am using is to use a Bound Service (I happen to be using one already so was spared the creation of extra boiler plate code.)
A Bound Service will only last as long as there are Activities bound to it. All the activities in my app bind to this service, so it lasts only as long as the user is actively using my application - therefore very much a real 'session'.
I start the tracker with a singleton instance of Application which I have extended and added a static getInstance() method to retrieve the instance:
// Non-relevant code removed
public IBinder onBind(Intent intent) {
tracker = GoogleAnalyticsTracker.getInstance();
tracker.startNewSession(PROPERTY_ID, MyApp.getInstance());
}
public boolean onUnbind(Intent intent) {
tracker.stopSession();
}
See: http://developer.android.com/guide/topics/fundamentals/bound-services.html
I did a time based split between visits in my app, working like this:
I've build a wrapper singleton Tracker object for the GoogleAnalyticsTracker where i keep the last time something got tracked. If that time's more then x seconds i treat it as a new visit.
Of course this is only useful if you track everything in your app, and may not be the best solution in every situation, works well for my app though.
It only supports trackPageView, but setCustomVar and trackEvent should be easily implemented..
Anywhere you need to track something just add the line:
Tracker.getInstance(getApplicationContext()).trackPageView("/HelloPage");
I usually do it in the onResume of an activity
Tracker gist
You will need something like this: http://mufumbo.wordpress.com/2011/06/13/google-analytics-lags-on-android-how-to-make-it-responsive/
That's on the previous version and used to work very well. Now I'm in the same struggle as you, as V2 doesn't seems to be very consistent.
I wonder if this is something that could be done using AOP.
Android can only use compile-time AOP methods so maybe something like AspectJ?
There's a little more info on using AspectJ in Android in this thread. The main issue being that you would still need to declare on classes you own.