Since Android introduced library projects, I've been converting my app into a library so that I can make several versions with appropriate tweaks (for example, a free and pro version using the same code base, but changing a few things).
I initially had trouble allowing the library project's code access to fields in my sub-projects. In other words, my free and pro versions each had a class with a few constants in them, which the library project would use to distinguish certain features.
In the sub-project, I extended the library's main activity and added a static initialisation block which uses reflection to change the values of fields in the library.
public class MyMainActivityProVersion extends MyMainActivity {
public static final String TAG = Constants.APP_NAME + "/SubClass";
static {
try {
ConstantsHelper.setConstants(Constants.class);
} catch (Exception e) {
Log.d(TAG, "--- Constants not initialised! ---");
e.printStackTrace();
}
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
}
In this code, ConstantsHelper is in the library, and I am providing Constants.class from my sub-project. This initialises the constants in the library project.
My approach works great, except for one particular use case. When the app hasn't been used in a while and it is 'stopped' by the OS, the static fields in ConstantsHelper are forgotten.
The constants are supposed to be reset by the main activity (as shown above), but the main activity isn't even launched because the OS resumes a different activity. The result of this is that the initialisation of the constants is forgotten and I cannot re-initialise them because the resumed activity is in the library (which has no knowledge of the sub-project).
How can I 'tell' other activities in the library to call code from sub-projects on resuming? Alternatively, is there a way to ensure that some code in my sub-project is called on every resume?
I think you're "cheating" by trying to share data across two Activities through static members. This happens to work when they're in the same, or related, classloaders. Here I believe Android uses separate classloaders for separate Activities, but, child Activities are in child classloaders. So ViewActivity happens to be able to see up into the parent classloader and see statics for the parent. Later I believe that parent goes away, and so your child re-loads MyMainActivity locally when you next access it and it's not initialized as you wanted. (Well, if it's not that, it's something very like this explanation.)
I think there are some more robust alternatives. You could use the LicenseChecker API to decide whether you're in a free or for-pay version rather than rely on details of the activity lifecycle and classloader. That's probably going to be better as it protects you from other types of unauthorized use.
I'm afraid I never found a good answer to this question. I'll probably continue with my terrible use of reflection and figure out some hacky workaround.
I felt I should come back and at least point out that I didn't solve this for the benefit of others who come to this page.
You can resolve this using Android resources.
Basically, define your constants in a resources xml values file in your Library project
E.g. "lib project"\values\constants.xml
<resources xmlns:tools="http://schemas.android.com/tools">
<bool name="const_free_version">false</bool>
<string name="const_a_constant">pippo</bool>
</resources>
Then, in your sub-project you can redefine the lib-project values using a different resources xml values file:
E.g. "sub project"\values\constants.xml
<resources xmlns:tools="http://schemas.android.com/tools">
<bool name="const_free_version">true</bool>
</resources>
In your lib project code when you refer to R.bool.const_free_version you get the actual value based on sub-project constant values xml.
Note that you don't have to redefine every values defined in the lib project constants.xml but only the ones you need different in your sub project.
Related
I'm not sure SO is the right place to ask this question so let me know if I should maybe post it on ProgrammersSE.
I've got an Android library project which comes with some functionality and some basic XML files. In the nearest future I'll be developing multiple apps which will heavily depend on that library - it's possible that some of them will only differ in that they'll be using different XML layout files and image resources. As far as I know Android will automatically pick the ones from the regular projects instead of the library one if the names of the appropriate files are the same so this shouldn't be a problem.
The problem is I expect that some of the projects will have to have a slightly extended functionality - meaning I'd have to, e.g., extend the classes which are in the library project.
I just tried that out but obviously that didn't work as I wasn't overriding the entire code of a class - just adding to it, meaning I seemingly can't have the the library Activities call the classes from my regular project.
Is there any way around that without using reflection?
Is there maybe a better way of handling such a situation?
Edit for clarification:
Thanks to #jucas and #Alex Cohn for the answers and the links. I'm not sure if the solutions you wrote are applicable to my situation - I'd probably have to see examples of those coded to decide if I can do anything similar in my project.
Here's an example of what makes this problematic for me: say in my library project I've got a class called MyActivity which extends Activity and implements OnScrollChangedListener because there's a ScrollView in it whose background has to scale. There could be something like this in it:
#Override
public void onScrollChanged() {
int currentScrollOffsetY = this.scrollView.getScrollY();
// No case for further back than the bottom of the screen (lower than 0)
// and if it's higher than where it should stop, keep it at that point
if (currentScrollOffsetY > this.screenHeightPx * MULTIPLIER_Y_ANIMATION_STOP) {
currentScrollOffsetY = (int) (this.screenHeightPx * MULTIPLIER_Y_ANIMATION_STOP);
}
// Set the pivot points of the background images
this.imageBackground.setPivotX(this.imageBackground.getWidth() / 2.0f);
this.imageBackground.setPivotY(0);
// Scale the background
float newBackgroundScale = 1 - (float) currentScrollOffsetY / (float) this.screenHeightPx;
if (newBackgroundScale < 0.75f) {
newBackgroundScale = 0.75f;
}
this.imageBackground.setScaleX(newBackgroundScale);
this.imageBackground.setScaleY(newBackgroundScale);
}
As you can see, the new scale for the background image is never smaller than 0.75 of the original size. Now if one of the projects using the library project needed that to be 0.8 instead, I could just move the value from the code to the XML values resources and it should be dynamically read from there - that's perfectly fine.
But what if I not only wanted to do that but also scale another ImageView?
this.imageBackground.setScaleX(newBackgroundScale);
this.imageBackground.setScaleY(newBackgroundScale);
this.differentImageBackground.setScaleX(newBackgroundScale);
this.differentImageBackground.setScaleY(newBackgroundScale);
How could this be achieved? I'm sorry if I don't understand this straight away - I've never done anything like this yet and some concepts are a bit difficult for me to get my head around them.
This a very common problem, and one that has several answers that might or might not be the best for your particular case, here are 2 from the top of my mind:
Develop a plugin architecture for your app, to load content and functionality from plugins Plugins architecture for an Android app?, note that this might be overkill if you just need to change a few classes here and there.
Modify your library project's architecture: This is one that I tend to use the most, just because it is simply and doesn't require a very complex refactoring. The steps needed for this are usually like this:
a. Figure out which parts of your activity or fragment might need to be extended by your main app project
b. Create interfaces and classes that implement those interfaces for the extendable functionality
c. This is the tricky part, isolate the creation and use of those classes in specific methods inside your activities or fragments
d. Finally on your main app project, create new classes that implement the same interfaces and override your fragments or activites to create these classes instead
I hope this helps you a bit, and if it doesn't you might want to sketch out some code in order to see exactly what problems you are having
This looks like a good fit for "inversion of control" design pattern. If a ExtendsActivity class is not changed between projects, but sometimes it uses an actor of class MyActor and sometimes ExtendsMyActor, then you should prepare a way for ExtendsActivity to accept the reference to such actor. You can inject this reference on construction, or later during the lifecycle of activity.
It is often recommended to use interface, i.e. define IActor and have both MyActor and any alternative implements this interface. But in some cases, extends fits perfectly, too.
I have just made my game app into a library so that I can incorporate it into a variety of "wrapper" projects, each with their own manifest file. This is so that I can easily maintain a variety of different versions e.g. free, paid, alternative markets etc.
I also wanted each wrapper to be able to affect the value of various boolean flags like include_adverts or allow_feature_x. I thought that a good way to do this (correct me if this is a dumb idea) would be for each wrapper project to have its own set of strings defined in its own strings.xml. So it could have things like:
<string name="allow_feature_x">true</string>
But now I have a problem scooping these strings from within the library.
I tried this:
boolean allow_feature_x = my_str2bool(getString(R.string.allow_feature_x));
But I get a allow_feature_x cannot be resolve error.
Can this strings.xml thing be made to work? Or was my scheme fundamentally flawed from the start?
This is exactly what I do for many of my projects.
Just make sure your library project has all the default set of strings defined in it, otherwise you won't be able to reference them, since the library does not know about the "parent".
Then override them in your "parent" projects. You only need to override the ones that are different from default, otherwise it will take the value from the library project.
Hope that makes sense.
Edit: By the way you can reference booleans like so:
<bool name="allow_feature_x">true</bool>
and then access getResources().getBoolean(R.bool.allow_feature_x);
I've searched around SO for this and found a few things, but I'm still not sure I fully understand, so I ask you for clarifications.
Here is what I need:
Have a project that has specific function: interrogate web service, display results in different views
Have a second, third and forth project that has exactly the same functionality as the first one, but only different graphic elements like splash screen image, icon, name, package name.
So, I have ProjectCore with activities and functionality. Project1 with a car icon and car image for splashscreen. Project2 with airplane icon and airplane image for splashscreen. Something like that. Each projects has a class with constants like'appId, appName, appServerURL"... All the web service call, data display is in Core as it's the same for all prohects, only the read is made from Constants class.
I was thinking of this approach
Make ProjectCore a Library project with a package like com.domain.core and dummy images
Make Project1, add reference to ProjectCore in it and with a package like com.domain.code.project1 and in resources folder, put images with same name as in core project
Make Project2 on the same principle like project1
Will this approach work ?
Thanks.
Later Edit. I've tried as mentioned before. For instance in Core project I had in drawable a file called splash.png. In Project1's and Project2's drawable folder I've put spash.png file with other images. This works fine. Running the Project1 and Project2 on my phone, started each app with it's own image. So far so good.
Then, because I have different constants I need to use in my App, I went into Core library project and added:
public class C {
public static String SomeConstant = "Project core!";
}
Here comes the problem, I need to have different constant values across Project1 and Project2. Because on Core project, the class is in com.domain.core.utils for instance... I can't add the same package in Project1 and Project2. How do I add the classes so I can update their values and be used on each project with particlar values ?
public class C {
public static String SomeConstant = "Project 1 constant!";
}
public class C {
public static String SomeConstant = "Project 2 constant!";
}
Thank you!
You want to create your functionality in a Library project and then have all of your Branded/OEM/3rdParty projects extend from this, overriding images and string resources where necessary.
When you need to use "Constants" you should instead have a single "run once" portion of your code (such as a splash screen) load these strings from resource files:
public static final String CONSTANT_ONE;
public void onCreate() { CONSTANT_ONE = getResources().getString(R.String.CONSTANT_ONE); }
EDIT
I'm unsure on how initialising a final value on onCreate() will perform. If final doesn't work well and you're worried about changing the variable during program execution then make the variable private (so only that class can assign to it) and then create a public static String getConstantOne() function.
Yes. Library projects are ideal for this, especially if only resources differ. I've used the exact approach that you've outlined with success...
Yes this should work fine. I did something a bit similar and I found occasionally you may have some circumstances where you want to call out from your library project to your application project. In these cases I used interfaces/abstract classes defined in the library project but implemented in application project...
How can I use an Android Library Project, and overwrite certain classes in the "final project".
In other words, I want to reference a library project, and at the same time, change some of the code..
Probably not quite what Android Library projects are all about but you could achieve that by using the interface and class inheritance features of the java language itself.
Library project seem to deal well with merging/overriding resources from the parent in the child and letting you use one codebase with varying app package names.
Given your updated comment about using library projects to have one edition of the app that uses AdMob and one that doesnt, I've revised this answer...
Assuming you dont mind packaging the AdMob library (~138KB) in with your full/paid edition, the simplest thing to do would be to extend an Applcation class and therein declare an enum and a static method to decide whether AdMob ads should be shown.
public class Application extends android.app.Application
{
public enum EditionType { LITE, FULL};
public static int getAdVisibilityForEdition(Context ctx)
{
if (EditionType.valueOf(ctx.getString(R.string.edition)) == EditionType.FULL)
{
return View.GONE;
}
return View.VISIBLE;
}
}
You'll add this class to the library project only. In all three of the projects you'll need to add an entry to /res/Strings.xml as such:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="edition">LITE</string>
</resources>
It doesnt matter what the value of this string is in the library project, but you'll vary it in the paid vs. ad supported editions using FULL and LITE respectively.
Finally, to make your activities responsive to these values call something like this in your onCreate() or onResume():
View ad = (View)this.findViewById(R.id.your_adMob_view_id)
ad.setVisibility(Application.getAdVisibilityForEdition
(this.getApplicationContext()));
This will work fine with AdMob as the AdMob code wont actually try to get an Ad if the view is not visible.
If you really dont want to have the extra 138K for the AdMob jar and the manifest permissions needed for it in the full edition, there are more elegant ways to do this but would involve using some kind of wrapper around the AdMob stuff and either varying the xml layouts between the editions (to include the AdMob view or not) or inject the AdMob view into the layout programatically.
Hope that helps.
I'm running into more and more naming clashes between Android activities and other classes. I was wondering if you could tell me how you avoid these. Sadly, my particular naming problems are not covered in the related questions on SO.
First example
I have an activity that displays a level of the game. However, the data required for that level (background artwork, entities etc.) is stored in a separate class. Naturally, I would call the latter class Level. However, I would call the activity Level as well, because it displays levels.
Second example
I have an activity that plays back a cut scene. It basically displays several images in a row. The information which image is shown for how long is stored in a separate class. As in the previous case, I would naturally call both classes CutScene.
How would you solve these naming issues? Name the activities LevelActivity and CutSceneActivity? Name the representation classes LevelModel and CutSceneModel? Something else?
I solve those problems by either prefixing or postfixing classes with their "type", like you suggested at the end of your question :
LevelActivity, GameActivity, MainActivity, ...
CommentsListAdapter, ...
CheckNewCommentsService, ...
and so on.
But I generally do an execption for the model classes, which are the objects that contain that data : I would still name my Level model class Level, and not LevelModel, to indicate I'm manipulating, and working with, a Level.
Another solution (longer to type ^^) might be to use fully-qualified names (see here) when referencing your classes :
com.something.yourapp.activity.Level
com.something.yourapp.model.Level
With this, you always know which class is really used.
In general the best way to name android application components is to add its "component type" as suffix.
Example :-
LevelActivity (LevelActivity extends Activity)
InboxUpdateService (InboxUpdateService extends Service)
ContactsContentProvider (ContactsContentProvide extends ContentProvider)
SMSBroadcastReceiver (SMSBroadcastReceiver extends BroadcastReceiver)
By naming using above method there will be minimal chances of losing track when you're working on big code flow with lots of similar names in your application.
So, name your Activities with suffix "Activity".
And name the Class which provides Data to your LevelActivity as Level.
In Contradiction to second part of Pascal MARTIN's answer, you can also use LevelActivity and LevelInfo together. Because they offer clear difference as quoted below:
Distinguish names in such a way that the reader knows what the
differences offer
- Robert. C. Martin, author of Clean Code
But the suffix are often redundant on cognitive basis. Using only the word Level clearly emphasises that class Level offers information about Level.
So, use Level for class that provides data about Level.
NOTE : If you're using suffixes, choose one word per concept.
For Example: If you're using the suffix Info to identify classes that offer information then only Info should be used (not Data or Model) throughout your application to avoid confusions.