Background
I'm trying to keep an app which is as modular as possible.
The app will have have tasks which it performs on different intervals. My goal is to make it as easy possible to add new tasks with minimal understanding of the underlying architecture and without having to modify other files but at the same time not over complicating the code.
It would be perfect if all you needed to do to add a new task is to create the file and that's it.
This will require loading the tasks in runtime which I don't really like, I could live with a single place where all the registration is done (this also enabled toggling of the task)
Right now I've have an abstract task class which has a piece of static code which registers all tasks (basically adds them to a list).
Problem
Each task will have their own set of preferences and possibly resources.
Dividing strings and array is pretty easy by using prefixes for the names but the main issue comes with the preferences.
Right now I'm using a PreferenceActivity to display my preferences.
The general settings are loaded from an XML file. Each task's preferences are located in a separate PreferenceScreen. All tasks have only one thing common and that is an "Enabled" checkbox.
I don't want to store all the preferences in one file as it has the possibility to get quite messy that way.
Current solution
Right now each task have a method setupPreferences(PreferenceScreen) in which they can add whatever options they want. This however has the downside of being programmatically which is not all that bad but I'd like to avoid that if possible.
Desired solution
The optimal solution would be if each task could have their own XML file which is loaded and added to the root PreferenceScreen, as far as I know however this is not possible, the only way to load it is into a PreferenceActivity.
Other notes
If anyone has any other suggestions on dividing resources in android feel free to share them :)
Thanks
Nicklas
Clarification
The tasks I'm talking about are never third party, they will internal only. This is more of a way to early on get a good structure of this app.
By using reflection I'm calling PreferenceManager.inflateFromResource(Context, int, PreferenceScreen) to create a PreferenceScreen from my XML files.
String resources are separated in separate files and prefixed with taskname_
Here is the code for inflating the PreferenceScreen, it should be placed in a PreferenceActivity:
/**
* Inflates a {#link android.preference.PreferenceScreen PreferenceScreen} from the specified
* resource.<br>
* <br>
* The resource should come from {#code R.xml}
*
* #param resId The ID of the XML file
* #return The preference screen or null on failure.
*/
private PreferenceScreen inflatePreferenceScreenFromResource(int resId) {
try {
Class<PreferenceManager> cls = PreferenceManager.class;
Method method = cls.getDeclaredMethod("inflateFromResource", Context.class, int.class, PreferenceScreen.class);
return (PreferenceScreen) method.invoke(getPreferenceManager(), this, resId, null);
} catch(Exception e) {
Log.w(LOG_TAG, "Could not inflate preference screen from XML", e);
}
return null;
}
Here is an example of how to use it:
package com.example;
import java.lang.reflect.Method;
import com.example.R;
import android.os.Bundle;
import android.preference.PreferenceActivity;
import android.preference.PreferenceManager;
import android.preference.PreferenceScreen;
import android.util.Log;
public class ExamplePreferenceActivity extends PreferenceActivity {
public static final String PREFERENCE_NAME = "ExamplePreferences";
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Sets the preference name
PreferenceManager pm = getPreferenceManager();
pm.setSharedPreferencesName(PREFERENCE_NAME);
// Adds default values and the root preference screen
PreferenceManager.setDefaultValues(this, PREFERENCE_NAME, MODE_PRIVATE, R.xml.preferences_layout, false);
addPreferencesFromResource(R.xml.preferences_layout);
PreferenceScreen root = getPreferenceScreen();
// Includes R.xml.other_preferences_layout and adds it to the bottom of the root preference screen
PreferenceScreen otherPreferenceScreen = inflatePreferenceScreenFromResource(R.xml.other_preferences_layout);
root.addPreference(otherPreferenceScreen);
PreferenceManager.setDefaultValues(this, PREFERENCE_NAME, MODE_PRIVATE, R.xml.other_preferences_layout, false);
}
/**
* Inflates a {#link android.preference.PreferenceScreen PreferenceScreen} from the specified
* resource.<br>
* <br>
* The resource should come from {#code R.xml}
*
* #param resId The ID of the XML file
* #return The preference screen or null on failure.
*/
private PreferenceScreen inflatePreferenceScreenFromResource(int resId) {
try {
Class<PreferenceManager> cls = PreferenceManager.class;
Method method = cls.getDeclaredMethod("inflateFromResource", Context.class, int.class, PreferenceScreen.class);
return (PreferenceScreen) method.invoke(getPreferenceManager(), this, resId, null);
} catch(Exception e) {
Log.w(LOG_TAG, "Could not inflate preference screen from XML", e);
}
return null;
}
}
This example would use res/xml/preferences_layout.xml as the root and then add res/xml/other_preferences_layout.xml to the bottom of the root.
Not exactly an answer to your question, but might be interesting anyway: have a look at plugin API for Locale and Tasker: http://www.twofortyfouram.com/developer.html
Locale and Tasker are phone automation apps. Both are highly configurable and modular, they accept third party plugins to extend their functionality. Similar to your case, each plugin has unique preferences. Their solution to the problem is, each plugin brings its own preferences activity, accessible using specific intent action. There are UI guidelines so that preferences screens look consistent.
Related
As a part of an app that I am developing there is request that it has to download JSON file that contains language translation that needs to be used in app instead of strings.xml file that is commonly used because this way any translation in app can be changed by updating external JSON file on some web portal and it avoid the need to make new build every time you want to change language translation.
I've already done this, and everything is working fine in a following way:
For example If I have button in xml once the activity starts I can reference the button and set the it's text from JSON that I've downloaded at the startup.
btnLogin = (Button) v.findViewById(R.id.btnLogin);
// reads translation from json that is stored in external application directory
btnLogin.setText(ResourceManager.getString("btnLogin"));
But my question is is there any way that I can avoid setting this text always from activity, can I somehow do it from XML file where this button is defined?
<Button
android:id="#+id/btnLogin"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="{ResourceManager.getString("btnLogin")}"/>
Is there any possibility that let's me call some function from xml and bind it's result to text attribute or is there any other way which avoids referencing all buttons/textviews and other controls from activity and setting text from there?
Have you tried overriding getResources() in you Application class and then
returning an extended version of Resources.
Ok, I tried this and it seemed to work for most of the resources in my xml file.
You may have to override this in more than one place. I just overrode it inside my Application class.
private MyResources resources;
#Override
public Resources getResources() {
if (resources == null) {
resources = new MyResources(super.getResources());
}
return resources;
}
class MyResources extends Resources {
public MyResources(Resources resource) {
super(resource.getAssets(), resource.getDisplayMetrics(), resource.getConfiguration());
}
public String getString(int resId) {
return "Bob"; // Use your ResourceManger here
}
}
This worked for me..
I want to display different preference options in my app depending on the device SDK and screen size, but certain preferences will be displayed on all devices. I could accomplish this by creating a full preferences.xml file for each possible device, like this:
xml/preferences.xml:
<PreferenceScreen>
<!-- Preference 1 (all devices) -->
<!-- Preference 2 (all devices) -->
</PreferenceScreen>
xml-v21/preferences.xml:
<PreferenceScreen>
<!-- Preference 1 (all devices) -->
<!-- Preference 2 (all devices) -->
<!-- Preference 3 (SDK 21 only) -->
</PreferenceScreen>
But this will get unwieldy very quickly given the number of possible combinations of screen sizes and SDKs. What I'd really like to do would be to use the same basic list of preferences on all devices and dynamically mix in additional preferences that are specific to certain screen sizes and SDKs. I've gone through the Android Providing Resources guide, but it seems that using alternative resources in the manner described there would still require me to create a separate resource directory for every screen-size-and-SDK combination and would require a lot of code duplication. Is there a nice, elegant solution to this problem that I'm missing?
Have you read the Settings guide? My app also has a lot of changes dynamically, both in 1) which headers / fragments to show, and 2) which prefs each fragment contains. For both issues you can use different resource versions, as you describe, or you can implement the differences in code.
For issue #1, you can either call loadHeadersFromResource directly, and have different headers resource files, or you can have code that does something similar. For example, my PreferencesActivity uses a separate PreferenceFragment subclass for each prefs section, and makes a decision at run time about which fragments (headers) to show:
#Override
public void onBuildHeaders(List<Header> targets) {
// Build a list of PreferenceFragment class objects to show now
List<Class<? extends PreferenceFragment>> fragmentClasses = ...;
// Create a Header for each fragment to return to Android
for (Class<? extends PreferenceFragment> fragmentClass: fragmentClasses) {
try {
PreferenceFragment fragment = fragmentClass.newInstance();
Header header = new Header();
header.fragment = fragmentClass.getName();
header.titleRes = fragment.getTitleId();
targets.add(header);
this.headers = targets;
} catch (Exception e) {
}
}
}
For issue #2, you can start with preferences from a common resource file, and then add the conditional ones in code:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Load common prefs from an XML resource
addPreferencesFromResource(R.xml.preferences);
// Add conditional prefs in code
PreferenceScreen prefScreen = getPreferenceScreen();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
Preference pref = ...; // create condition pref
prefScreen.addPreference(pref);
}
}
I actually add all prefs in code, common and conditional. However, I didn't see a way to create a PreferenceScreen from scratch, so I actually have an empty XML file that I load from resources, and then add all preferences in code. It works really well.
Hi I want to create preferences in my application but I cannot use resources at all due to some dependency issues.
I am able to do this using the below code:
public class DTMainActivity extends PreferenceActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setPreferenceScreen(defaultPref());
setDependencies();
}
// The first time application is launched this should be read
private PreferenceScreen defaultPref() {
PreferenceScreen root = getPreferenceManager().createPreferenceScreen(this);
SwitchPreference dLogTracingEnablePref = new SwitchPreference(this);
dLogTracingEnablePref.setTitle(R_Class.R_String.dLogTracingEnablePrefString);
dLogTracingEnablePref.setDisableDependentsState(false);
dLogTracingEnablePref.setChecked(true);
dLogTracingEnablePref.setKey(R_Class.R_String.dLogTracingEnablePrefKey);
root.addPreference(dLogTracingEnablePref);
}
I would want to do this using the new fragment based approach, without using the deprecated APIs like getPreferenceManager etc.. I can create all the other UI layout elements like linearlayout etc.. without any resources, but when it comes to preferences and PreferenceFragment class, all that is available is addPreferencesFromResource() which would need an XML. Can any one help me here please?
I managed to made it using a PreferenceFragment, without addPreferencesFromResource(),
Instead I just created the PreferenceScreen like you just did and used
try using the bindPreferenceSummaryToValue, consider "p" being a PreferenceScreen with Preferences already added into it, (and also that has been created and configured previously)
PreferenceScreen p = createPreferences();//a method that creates a PreferenceScreen and add some preferences into it
this.setPreferenceScreen(p);
bindPreferenceSummaryToValue(p.findPreference("preference_key"));
I responded to someone with a similar problem here .. perhaps you can check it out
Is there any way I can add items in code to a String-Array resource? For instance, if I want to create a spinner that shows the user values, and I want to allow the user to add their own custom values.
No. this is not supported because resources are packaged in the binary .apk and as such cannot be changed.
Don't follow this design pattern, change your approach.
probably JoxTraex has no idea on android framework or as they are saying:
when someone says this can't be done - there always is someone who doesn't know that and will do it :)
so to the point:
Resources is an open class is just a wrapper around ResourcesImpl class
(with depreciated constructor - but is available)
public Resources(AssetManager assets, DisplayMetrics metrics, Configuration config)
in app every call to it is made via Context
so:
1) you always can provide your own Resources implementation
2) or (when its enough) your own Context implementation (so sky is the limit)
... i now feel eyes on "Context implementation" - yeah you can even replace top most context in LoadedApk, Activity, etc.. yeah cool? but when, how? how: via reflections, when: good app designer knows that this place is where (before) first/any call to such object is made ...
But there is one dangerous catch here - in one case android straight unwrap Context to ContextImp and then you need to return those Context instead yours - where is the place that i'll keep as secret for those who like riddles :)
there is also in Resources
a) a hidden constructor (best entry point as it is not making any ReourceImpl (which can be set by method mentioned bellow)
public Resources(#Nullable ClassLoader classLoader)
b) hidden method
setResImpl(ResourcesImpl)
sum up:
so by calling
Resources.getStringArray(**not existing in APK resource id here**);
on our own implementation we could get whatcha want :)
even better we can add to our implementation
addResourceById(int,Object)
method and add new resources :) then with assign ability check on resource we will use and do a up casting to our implementation :) to use our newly added method :)
btw: if someone says to you "you shouldn't do it - blablabla" this is the best reason to do it! - if not permitted by law :)
enough the theory go to a practice:
example Context implementation forwards calls to getString:
public class MyContext extends Context {
....
// override context get resources method
#Override
public android.content.res.Resources getResources() {
// get super resources
android.content.res.Resources resources = super.getResources();
// pull assets
android.content.res.AssetManager assets = resources.getAssets();
// pull metrics
android.util.DisplayMetrics displayMetrics = resources.getDisplayMetrics();
// pull configuration
android.content.res.Configuration configuration = resources.getConfiguration();
// construct new anon resource implementation
return new android.content.res.Resources(assets, displayMetrics, configuration) {
// overrride interesting method
#android.support.annotation.NonNull
#Override
public String getString(int id) throws android.content.res.Resources.NotFoundException {
return id == pl.ceph3us.base.common.R.string.my_sweet_google
? "fck_you_google";
: super.getString(id);
}
};
}
}
I’m a new developer, so my question maybe too much basic.
I look for example of defining preferences of sound. User can choose what kind of sound will start an application, for example. There can be such a RingtonPreference widjet, so user can choose a sound.
As I know, preferences support the primitive types: Boolean, string, float, long and integer. What way is the best to design preferences: store in entryValues the names if sounds (strings), the address of files from Resourse class (integer), or other way.
Please provide a short example of code.
Thanks in advance!
First of all thank you for the quick and detailed answer!
I want to arrange list of sounds: there must be one “None”, list of sounds that contains folder “raw”, option to add a new sound from different locations and two buttons: “set” and “cancel”. When user touches one item from the list – sound starts to play.
There is a little problem with standard widget that provide android library. “ListPreference” isn’t appropriate because on touch on one of the items – item is chosen and list closes, “there are not buttons set and cancel”.
“RingtonPreference” isn’t appropriate as well – I didn’t succeed to add something to list.
How is possible to build a custom preference layout and that is options that were chosen will be saved as well as on standard widgets. Please provide a short code example. Thanks in advance!
I think the best way to store the Resource are by integers. or you could do names.
I think integer is more reliable.
So to use SharedPreference with this you will need to get access to the apps SharedPreference
public class PreferencesDemo extends Activity {
SharedPreferences app_preferences;
private int resourceNumber;
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// Get the app's shared preferences
SharedPreferences app_preferences = PreferenceManager.getDefaultSharedPreferences(this);
resourceNumber = app_preferences.getInt("resourceNumber", 0);
if(resourceNumber == 0){
//This means the user hasnt selected a song and you must act accordingly. Or put a resource number where the 0 is do set it to a default song
}
You will probably want to create a method to put the songs in the SharedPreference such as;
private void createSongResouces(){
SharedPreferences.Editor editor = app_preferences.edit();
//Here you can put as many songs as you want just make sure you call editor.commit(); as i do.
editor.putInt("resourceNumber", resourceNumber);
editor.commit(); // Very important
}