I have 2 versions of an app, free and paid, but have been maintaining the code separately. I've finally moved the code into a library referenced by both to make maintaining the code easier.
I found that changing the AppwidgetProvider caused the launcher to delete any existing widgets, so I moved those classes back out of the library to keep the provider the same so users don't have to recreate their widgets. The launcher no longer deletes the widgets, but instead, they simply don't appear after updating.
If I call AppWidgetManager.getAppWidgetIds for the componentname, as it's always been, the appwidgetid is still there. The appwidgetprovider and service still get called to update the widget, and /data/system/appwidgets.xml still shows the widget, but the launcher never displays it.
It's not that it's invisible, as long pressing in the widget location brings up the wallpaper chooser. I can create new widgets just fine, but I don't want to frustrate users by asking them to recreate their widgets. The logs don't show any errors thrown by the launcher or AppwidgetService.
Any ideas why the widget stops rendering after updating? It's somehow related to moving most of the code into a separate library. Thanks!
Edit: I'm testing on an emulator, api level 15, stock launcher
OK, I found a solution, but I feel sick for what it is...
After reading about how classes declared in the manifest should never change, I went and creating each class in the manifest as a class in the app, extending the corresponding class in the library. Then, I had to change every Intent to include the correct class, using forName. So, an example of this scenario is:
app package: com.sample.package
activity: MyActivity
library: com.sample.package.core
activity: MyActivity
There's a MyActivity in the app, and a MyActivity in the library. The MyActivity in the app simply extends com.sample.package.core.MyActivity.
Then, any occurrence of...
new Intent(context, MyActivity.class)
...in the library must become...
new Intent(context, Class.forName(context.getPackageName() + "." + MyActivity.class.getSimpleName());
If there's a way to do this through the manifest, please let me know!
Related
I have a strange behavior of Activity in complicated legacy code.
In LoginActivity I want to start some PromotionActivity but just after onResume it goes to onPause.
As this app has some ActivityUtils in one of its numerous libraries - I used another approach:
I called one default PublicActivity and passed a fragment there.
It looks like
Map<String, String> params = new HashMap<>();
params.put("fragmentName", PromotionFragment.class.getName());
ActivityUtils.showActivity(activity, PublicActivity.class, params);
And all is shown without problems.
To understand why this works, I copied PublicActivity from a library to the main folder as a PublicActivity2 and tried to launch it but failed again. I did different changes, even extended PublicActivity2 from PublicActivity
public class PublicActivity2 extends PublicActivity {}
ActivityUtils.showActivity(activity, PublicActivity2.class, params);
but again failed.
How is it possible that PublicActivity is shown without problems but PublicActivity2 which is extended from PublicActivity and used in the same showActivity method - only goes to onResume and immediately goes to onPause?
I found the issue. As it's complicated legacy code with numerous libraries I didn't notice that there is some Application.ActivityLifecycleCallbacks in one of them which listen lifecycle of activities and compare every launched activity with a list of permitted ones. So, I found that list, added my activity and all works fine.
I didn't find it quickly because it was passed from a library to main app as another interface which extends needed one, luckily searching through the app with a word Lifecycle I found getApplication().registerActivityLifecycleCallbacks(new Analytics().getActivityLifecycleCallbacks())
So, for this weird situation when your activity immediately finishes after launching without any visible reason - my advice is to look for this ActivityLifecycleCallbacks
I found recently that after I renamed my class that extends AppWidgetProvider (as part of a code tidy-up), and installed the modified app, existing widgets would break, displaying just "Problem loading widget".
Adding a fresh widget to the home screen works fine, it's just existing widgets that break. Whilst for me as developer I don't mind deleting existing widget and adding a new one, the users of my app would not take kindly to having to do this because each widget takes time to reconfigure.
So, is there a way of safely renaming an AppWidgetProvider class without breaking existing widgets? I could of course just keep the name as it is, but it gives me an uneasy feeling that I'm stuck with the (in hindsight slightly confusing) name forever.
Unfortunately, there is no way to fix it. The problem is in changed ComponentName. AppWidgetHost just trying to get your widgets from AppWidgetManager, and AppWidgetManager trying to get previous widgets by ComponentName, so after changing package name of yours AppWidgetProvider, AppWidgetHost can't restore your previous widgets, because he got saved old ComponentName. So as you can see, there is no way to change a package name of AppWidgetProvider.
I came across a problem in a job interview that whether I can dynamically add a new activity to an Android application without releasing a new version of the app. And he told me that there exists certain mechanism that we could dynamically change the Activity to a new one, without registering in the AndroidManifest.xml file. I searched some documents, but did not found possible way to do this.
Can I start an Activity without registering in the AndroidManifest.xml file? And is it possible to dynamically modify the existing Activity?
Can I start an Activity without registering in the AndroidManifest.xml file?
No. I don't think you can start an activity that is not registered in the AndroidManifest.xml file. The manifest file keeps track of the activities that the app can use upon compiling/building the application. Any attempt to open an unregistered activity will result in an application crash.
And is it possible to dynamically modify the existing Activity?
As Bette Devine said, you can change the layout of the existing activity by calling setContentView(R.layout.new_layout); based on some user action (like a button press). However, calling setContentView more than once in your activity is a bad practice that people generally avoid doing. It is not recommended since you'll have to write code that would manage user interaction for the second layout. Imagine writing two activity codes in one java file. That would result in unnecessary clutter of code when you can just write them separately.
Yes it is possible to dynamically modify an existing one.
Here modification does not mean that you are changing the name of an activity but means that you are changing the content.
Just call the setContentView method of activity to give a new layout to the activiy and you whole activity now will be hosting a different content.
setContentView(R.layout.new_layout);
I've had an "On/Off" sessions with Android for the past three years, climaxing with an app which can be described as a "Big ball of mud".
In order to avoid such travesties, I decided to use Android Bootstarp as a basis for expansion and a guide for good working conventions.
I think I managed to grasp the basic logic behind dagger (which is used for injecting modules and classes throughout the app), but as stupid as it may seem, I can't seem to grasp how/where or when the login Activity is called:
The "home" activity (the one with the launcher/home intent filters assigned) is called CarouselActivity, and is used to show all the content you get after login (user, news and checkins fragments). No reference to the login activity.
The login activity is called BootstrapAuthenticatorActivity
The only reference to it is in the BootstrapModule class, where it's added to the "injects" member of the #Module annotation.
Also, here's a cryptic quote from the AndroidManifest.xml file:
<activity
android:name=".authenticator.BootstrapAuthenticatorActivity"
android:excludeFromRecents="true" >
<!--
No intent-filter here! This activity is only ever launched by
someone who explicitly knows the class name
-->
</activity>
This isn't helpful...
Ok, so no direct callback, no intent filters, and my IDE's "Find Usage" button doesn't help either(shows usages of the class only withing the same class...)
This leaves me with two possible explanations:
1. This has something to do with injections, which I fail to understand.
2. Something else altogether, which I completely missed.
After rummaging through just about every single class in the project, and another couple of hours spent on traceview, I found this on the Android Bootstrap Google Group.
Bootstrap uses Android's built in authentication framework, which here means that there's a service defined in the app which checks whether there are saved API keys or login details, and if not it starts the login activity.
So to sum it up, here's a nice list partially describing the authentication lifecycle:
1.The login Activity is called from an intent the BootStrapAccountAuthenticator class addAccount method (which extends the AbstractAccountAuthenticator class)
2.The BootStrapAccountAuthenticator is added to the AccountAuthenticatorSerivce, which returns the authenticator when onBind(); is called (the service has an intent filter for android.accounts.AccountAuthenticator)
4.Another class, called ApiKeyProvider, calls the AccountManager.getAuthTokenByFeatures(), which in turn binds to the AccountAuthenticatorSerivce) and returns the key it got.
5.BootstrapServiceProvider returns in its getService(); method a new BootstrapService created with the key retrieved by ApiKeyProvider (BootstrapService is a POJO implementing HTTP requests using kensawiski's http-request library).
6.Each fragment class (a total of three in this case) calls the BootstrapServiceProvider.getService(); method in the onCreateLoader method.
That's it.
Regarding the usage of dagger, everything is injected retroactively from the fragments themselves down to, and including, the AccountManager.
I assume this could allow for relatively modular code (for example, you could swap out the ApiKeyProvider with a different one).
I hope this is even remotely clear, and maybe even possibly of help to anyone.
I might come back later and "refactor" this answer...
I'm trying to turn the stock ICS launcher into a standalone app. I'm nearly there - the only things not working are the search icon and dropping widgets onto the screen, which causes a crash.
The crash is because the stock launcher uses appWidgetManager.bindAppWidgetId(appWidgetId, componentName); to add widgets, which apparently only system apps have permission to do.
So my question is, what is the correct way for a non-system app to add widgets and acheive the same UI experience as the stock ICS launcher?
Timmmm,
Your issue is that you are looking to the wrong object. You can't really control the AppWidgetManager. Its not your job, its the System's. What you CAN do is control an AppWidgetHost, it just requires a few semantics. Here are the basics.
EDIT: Extra Background on the Widget Binding Process
The AppWidgetManager is a singleton object that runs when the System is started. This means that every instance of every launcher uses the same AppWidgetManager. What differentiates them is their AppWidgetHost and the RemoteViews they are currently holding. The AppWidgetManager basically keeps a list of all of the active hosts and the widgets they are holding. An AppWidgetHost is not a priveleged object. That is, any activity may have a single host. Thus, an entire application may be nothing but Widgets, if they so choose.
When you instantiate the Host, you must then add Views to it. So, basically it is a list of child Views with no mandatory parental bounds, except what your Activity gives it. First, you ask for an ID (via myHost.allocateAppWidgetId()). Then you use your Pick Widget Activity/Dialog. The Dialog returns the WidgetInfo. The View is retrieved when you ask the Host to create the View (via createView) with the WidgetInfo and the ID you asked for. It then asks the widget for its RemoteView.
Finally, you bind the widget by placing the View in your Activity as a Child. This is done via the addView() method of the ViewGroup that holds all of your Widgets.
The Process in Action (EDITED)
First, you have to make sure you have this in your android manifest:
<uses-permission android:name="android.permission.BIND_APPWIDGET" />
Next, you have to create an AppWidgetHost (I extend my own for my launcher). The key to the Host is to keep a reference to the AppWidgetManager via AppWidgetManager.getInstance();.
AppWidgetHost myHost = new AppWidgetHost(context, SOME_NUMERICAL_CONSTANT_AS_AN_ID);
Now, get your ID:
myHost.allocateAppWidgetId()
The next step is done by whatever method you use to get the widget info. Most times it is returned via an Intent through onActivityResult. Now, all you really have to do is use the appInfo and create the view. The WidgetId is normally provided by the pick widget activity result.
AppWidgetProviderInfo withWidgetInfo
= AppWidgetManager.getInstance().getAppWidgetInfo(forWidgetId);
AppWidgetHostView hostView
= myWidgetHost.createView(myContext, forWidgetId, withWidgetInfo);
hostView.setAppWidget(forWidgetId, withWidgetInfo);
Now you just bind the View as a child to whatever you want to bind it to.
myViewGroup.addView(hostView);
Of course, you always have to consider where and how to place it, etc. Also, you have to make sure that your AppWidgetHost is listening before you start adding widgets.
myHost.startListening()
To Summarize
The Widget binding process spans many methods and steps, but all occurs through the AppWidgetHost. Because Widgets are coded outside of your namespace you don't have any control except for where you put them and how you size the View. Since they are ultimately code that runs in your space but outside of your control, the AppWidgetManager acts as a neutral mediator, while the AppWidgetHost serves as the facilitator on your app's behalf. Once this is understood, your task is simple. The steps above are all the required steps for any custom launcher (including my own).
EDIT: Final Clarification
The ICS Launcher does this as well. The appWidgetManager they use is just a wrapper housing the AppWidgetHost and the calls to the AppWidgetManager. I forget that very little of this is explained on the Android Development Central website.
Hope this helps! Let me know if you need anymore details.
FuzzicalLogic
I now know the definitive answer. In Android 4.0, you can't do it. I ended up making my users pick the widget twice, which sucks, but there is no way around it.
In Android 4.1 they fixed the problem!
SDK apps can now host widgets and don't have to use the rubbish widget picker API! You can look into the Jellybean Launcher2 source code for details, but basically, when you first try to bind a widget, Android will pop up a dialog box saying "Do you want to allow this app to bind widgets", and then the user can decide to give it permission or not.
I'm not sure why they went for the modal permission-granting dialog box rather than the all-permissions-on-install model they've used for everything else, but whatever, it works!
Now we just have to wait 4 or 5 years until everyone has Android 4.1 or greater!
I just found this tutorial on how to add appwidgets to normal apps, which might help: http://coderender.blogspot.com/2012/01/hosting-android-widgets-my.html
This tutorial still uses the "AppWidget Picker" list, so it might not work for you since ICS has the widgets picker inside the app drawer itself.
Still, was worth to mention since tutorials on hosting widgets are very rare :)
Cheers,
Yuvi
Fuzzical Logic,with your code below,
AppWidgetProviderInfo withWidgetInfo
= AppWidgetManager.getInstance().getAppWidgetInfo(forWidgetId);
AppWidgetHostView hostView
= myWidgetHost.createView(myContext, forWidgetId, withWidgetInfo);
hostView.setAppWidget(forWidgetId, withWidgetInfo);
if have not the permission of bind_widget,widgethost got nothingļ¼cus withwidgetinfo is null,widgethost create nothing.