As I was following an old tutorial (Créez des applications pour Android -> openclassroom) I got stuck on this deprecated method addPreferencesFromResource(int id) from the PreferenceActivity class.
So my question is :
What is the new way of creating Preferences in Android ?
I found this post (What to use instead of “addPreferencesFromResource” in a PreferenceActivity?) that help me understand that you have to go through a PreferenceFragment in order to do it.
In the following explanation I use your.package. just to show that you have to put the package name. Everybody has its own package so please replace it with your package.
lets begin :
1. Preference Fragment
Create your PreferenceFragment class
MyPreferenceFragment
public class MyPreferenceFragment extends PreferenceFragment
{
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.fragment_preference);
}
}
Then the associated xml resource
fragment_preference.xml (in the folder res/xml of your project)
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android">
<PreferenceCategory
android:title="FOO">
<CheckBoxPreference
android:key="checkBoxPref"
android:title="check it out"
android:summary="click this little box"/>
</PreferenceCategory>
</PreferenceScreen>
That's all for the Fragment part.
2. Preference Activity
Create the PreferenceActivity class
MyPreferenceActivity
public class MyPreferenceActivity extends PreferenceActivity
{
#Override
public void onBuildHeaders(List<Header> target)
{
loadHeadersFromResource(R.xml.headers_preference, target);
}
#Override
protected boolean isValidFragment(String fragmentName)
{
return MyPreferenceFragment.class.getName().equals(fragmentName);
}
}
Do not forget to override isValidFragment(String fragmentName) method as you will get punched in the face by your application ! ;) More seriously I have no idea why you need to do this but it is needed. If someone has an explanation about this I'd gladly read it :)
EDIT :
Thanks to kirtan403 I now know why it is needed : it has to be set because of an (android framework fragment injection).
As you can see in the onBuildHeaders(List<Header> target) we load another xml file that contain the headers of the preference. In short, headers are the left part of the preference and the fragment are the right part (for tablet). For a phone you will first have the headers and when you click on an item the corresponding fragment will be put on top of the headers list.
Read this article (Multi-pane development in Android with Fragments - Tutorial) the images explain themselves.
Then the associated xml resource
headers_preference.xml (in the folder res/xml of your project)
<?xml version="1.0" encoding="utf-8"?>
<preference-headers
xmlns:android="http://schemas.android.com/apk/res/android">
<header
android:fragment="your.package.MyPreferenceFragment"
android:title="Goto: Preference fragment"
android:summary="An example of some preferences." />
</preference-headers>
As you may have noticed in the header section you have :
android:fragment="your.package.MyPreferenceFragment"
This will act as a Link to the fragment you want to show. On Tablet it will load on the right part and on the phone it will load on top of the current view.
3. Android Manifest
Now what you should do is to add your Activity to the AndroidManifest.xml file.
Inside the application section add these lines :
<activity
android:name="your.package.MyPreferenceActivity"
android:label="Preferences">
</activity>
You will probably tell me :
"Oh darling you forgot to put android:launchMode="singleTask" in your actvity"
But DO NOT PUT THIS as you will never load your fragment on phone. This error was solved by a great man ! This is the link to his blog (Android header preferences on small screen/phone).
4. Start the Preferences from Menu
Finally you need to add the ability to show this Preference !! To do so you will need 3 things :
The Menu
menu.xml (in the folder res/menu of your project)
<?xml version="1.0" encoding="utf-8"?>
<menu
xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="#+id/preferences"
android:title="Preferences" />
</menu>
Loading this Menu in your Main activity (not the PreferenceActivity) under the method onCreateOptionsMenu(Menu menu)
#Override
public boolean onCreateOptionsMenu(Menu menu)
{
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.menu, menu);
return true;
}
Starting the MyPreferenceActivity Activity when you click on that button.
For that you will need to override the onOptionsItemSelected(MenuItem item) method in your Main activity.
#Override
public boolean onOptionsItemSelected(MenuItem item)
{
switch(item.getItemId())
{
case R.id.preferences:
{
Intent intent = new Intent();
intent.setClassName(this, "your.package.MyPreferenceActivity");
startActivity(intent);
return true;
}
}
return super.onOptionsItemSelected(item);
}
Et voila les amis !
I haven't tested this code. I took it and modified it from my own code so I may have not well copy pasted things. If you encounter errors tell me, I'll try to figure out the problem and fix this.
I hope this post will help some people out there :D
Cheers !
I liked the solution from this post: http://alvinalexander.com/android/android-tutorial-preferencescreen-preferenceactivity-preferencefragment
.. because it seems the most compact for someone that just needs something very basic up and running quickly. It has only one .java file and two small xml files.
Activity Config REMINDERS
After adding the 3 files to your project, Don't forget to
A) Add the Prefs Activity to Manifest file
B) Add some way to launch the Prefs Activity .. e.g., a Button or Menu item
Add the following files to your project. Use the order they are listed in to avoid compile errors.
Add /res/values/array.xml
<resources>
<string-array name="listArray">
<item>Ace</item>
<item>Club</item>
</string-array>
<string-array name="listValues">
<item>Ace</item>
<item>Club</item>
</string-array>
</resources>
Add /res/xml/preferences.xml
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<EditTextPreference android:title="Your Name"
android:key="username"
android:summary="Please provide your username"></EditTextPreference>
<CheckBoxPreference android:title="Application Updates"
android:defaultValue="false"
android:summary="This option if selected will allow the application to check for latest versions."
android:key="applicationUpdates" />
<ListPreference android:title="Download Details"
android:summary="Select the kind of data that you would like to download"
android:key="downloadType"
android:defaultValue="Ace"
android:entries="#array/listArray"
android:entryValues="#array/listValues" />
</PreferenceScreen>
Add the Activity code
public class AppPreferenceActivity extends PreferenceActivity
{
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
getFragmentManager().beginTransaction().replace(android.R.id.content, new MyPreferenceFragment()).commit();
checkValues();
}
public static class MyPreferenceFragment extends PreferenceFragment
{
#Override
public void onCreate(final Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.preferences);
}
}
private void checkValues()
{
SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(getBaseContext());
String strUserName = sharedPrefs.getString("username", "NA");
boolean bAppUpdates = sharedPrefs.getBoolean("applicationUpdates",false);
String downloadType = sharedPrefs.getString("downloadType","1");
String msg = "Cur Values: ";
msg += "\n userName = " + strUserName;
msg += "\n bAppUpdates = " + bAppUpdates;
msg += "\n downloadType = " + downloadType;
Toaster.shortDebug(msg);
}
}
Related
First question on stackoverflow, please forgive any inconsistencies.
My Android app implements a PreferenceFragmentCompat fragment which is opened through a button click in the MainActivity. All the options in "root" PreferenceScreen work smoothly but I cannot open any "child" PreferenceScreens.
After a lot of search, I found the need to implement the onPreferenceStartScreen callback in my fragment and it did work! But now, I made quite a few changes to the app and must have screwed something up and can't figure out what.
So here it goes!
Among others, I implement these 2 libraries in my app level gradle.build
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.preference:preference:1.1.1'
This is my test pref3.xml
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<CheckBoxPreference
android:defaultValue="false"
android:key="check_box_preference_1"
android:title="Check box preference" />
<PreferenceScreen android:title="Preference Screen">
<CheckBoxPreference
android:defaultValue="false"
android:key="check_box_preference_2"
android:title="Check box preference" />
</PreferenceScreen>
</PreferenceScreen>
This is my test java Preferences fragment (Common.log is my utility logger method)
public class TestPrefFrag extends PreferenceFragmentCompat implements PreferenceFragmentCompat.OnPreferenceStartScreenCallback {
private static final String TAG = "TestPrefFrag";
#Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
Common.log(5, TAG, "onCreatePreferences: started");
setPreferencesFromResource(R.xml.prefs3, rootKey);
}
#Override
public boolean onPreferenceStartScreen(PreferenceFragmentCompat caller, PreferenceScreen preferenceScreen) {
Common.log(5, TAG, "onPreferenceStartScreen: '" + caller.getTag() + "' called for key '" + preferenceScreen.getKey() + "'");
caller.setPreferenceScreen(preferenceScreen);
return true;
}
#Override
public void onNavigateToScreen(PreferenceScreen preferenceScreen) {
Common.log(5, TAG, "onNavigateToScreen: called for key '" + preferenceScreen.getKey() + "'");
//getCallbackFragment();
super.onNavigateToScreen(preferenceScreen);
}
#Override
public boolean onPreferenceTreeClick(Preference preference) {
Common.log(5, TAG, "onPreferenceTreeClick: detected click # '" + preference.getKey() + "'");
return super.onPreferenceTreeClick(preference);
}
}
When I run this
clicking the CheckBoxPreference in the root screen calls the
onPreferenceTreeClick only
clicking the PreferenceScreen in the root screen calls the onNavigateToScreen and then the onPreferenceTreeClick but never the onPreferenceStartScreen
Shouldn't onPreferenceStartScreen be called right after onNavigateToScreen.
What am I doing wrong?
Thanks for any help!
You need to override the PreferenceFragment method getCallbackFragment, like this
#Override
public Fragment getCallbackFragment() {
return this;
}
I'm trying to learn ways to build preference pages in the Xamarin Android application.
I found a lot of examples with PreferenceFragment but it was marked as deprecated and it is difficult for me to rewrite them on the current stage.
I've created activity to represent headers. I added IntentFilter so I can access this activity from apps list in the settings menu. Also it has internal class to group some preferences together:
namespace droid.examples.Preferences
{
[Activity(Label = "Settings activity", Theme = "#style/AppTheme", Name = "droid.examples.Preferences.SettingsActivity")]
[IntentFilter(new string[] { "android.intent.action.APPLICATION_PREFERENCES" })]
public class SettingsActivity : PreferenceActivity
{
public override void OnBuildHeaders(IList<Header> target)
{
base.OnBuildHeaders(target);
LoadHeadersFromResource(Resource.Xml.preference_headers, target);
}
public class SettingsFragment : PreferenceFragmentCompat
{
public override void OnCreatePreferences(Bundle savedInstanceState, string rootKey)
{
// Load the Preferences from the XML file
SetPreferencesFromResource(Resource.Xml.app_preferences, rootKey);
}
}
}
}
My app_preferences.xml which I can't open by selecting "Prefs 1" header from preference_headers.xml:
<?xml version="1.0" encoding="utf-8" ?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<PreferenceCategory android:title="Category">
<CheckBoxPreference
android:key="checkbox_preference"
android:title="Developer mode"
android:summary="Allow user to see detailed messages" />
</PreferenceCategory>
</PreferenceScreen>
I have preference_headers.xml. It opens when I click on gear wheel near application name. It looks like this:
<?xml version="1.0" encoding="utf-8" ?>
<preference-headers xmlns:android="http://schemas.android.com/apk/res/android">
<header android:fragment="droid.examples.Preferences.SettingsActivity.SettingsFragment"
android:title="Prefs 1"
android:summary="An example of some preferences." />
</preference-headers>
My package name: droid.examples
I think that one problem related to the android:fragment attribute value.
What is the rules to build that value?
I suppose that it must start from 'package name'. Should it contain namespace between class name and package name?
What does $ mean in the attribute value? Is it used to mark internal class? I saw in the several places next code:
android:fragment="com.example.android.apis.preference.PreferenceWithHeaders$Prefs1Fragment"
I hope you can help me find where I made a mistakes.
Source code from GitHub
I spend a lot of time to investigate that issue and I want to make a summary.
We have to override IsValidFragment method in the SettingsActivity:
protected override bool IsValidFragment(string fragmentName)
{
return fragmentName == "droid.examples.preferences.SettingsActivity.SettingsFragment";
}
My SettingsActivity extends PreferenceActivity. Thanks to #Jeremy advice about implementation of IOnPreferenceStartFragmentCallback I find out that base class already extends it.
public abstract class PreferenceActivity ...
{
...
public virtual bool OnPreferenceStartFragment(PreferenceFragment caller, Preference pref);
...
}
So, I probably need to use PreferenceFragment instead of PreferenceFragmentCompat to make code consistent:
public class SettingsFragment : PreferenceFragment
{
public override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
AddPreferencesFromResource(Resource.Xml.app_preferences_for_header);
}
}
Also we have to add Register attribute to our fragment:
[Register("droid.examples.preferences.SettingsActivity.SettingsFragment")]
public class SettingsFragment : PreferenceFragment
{
}
Finally I updated preference_headers.xml
<?xml version="1.0" encoding="utf-8" ?>
<preference-headers xmlns:android="http://schemas.android.com/apk/res/android">
<header android:fragment="droid.examples.preferences.SettingsActivity.SettingsFragment"
android:title="Prefs 1"
android:summary="An example of some preferences." />
</preference-headers>
android:fragment attribute value can contains '$' but '+' won't work because Register doesn't support it and we will get compilation error.
Thanks everyone who tried to help me
Looks like the string you provide is just a message to send to your parent activity. Your parent activity is responsible for instantiating the right Fragment, and performing the ceremony to render it.
The platform docs seem to indicate as such:
When a user taps a Preference with an associated Fragment, the interface method PreferenceFragmentCompat.OnPreferenceStartFragmentCallback.onPreferenceStartFragment() is called
At time of writing, there's a code snippet on that page, which I've translated for my own project more-or-less as follows:
// This has to go in the activity which hosts the PreferenceFragment
// which owns the Preference that has the `android:fragment` attribute.
using Android.Support.V7.Preferences;
using Android.Support.V4.App;
partial class MyActivity :
PreferenceFragmentCompat.IOnPreferenceStartFragmentCallback
{
Fragment GetFragmentForPrefString(string prefFragment)
{
// you implement this
}
const int fragmentContainerId = Resource.Id.whatever;
public bool OnPreferenceStartFragment(
PreferenceFragmentCompat caller, Preference pref)
{
string prefString = pref.Fragment;
var transaction = SupportFragmentManager.BeginTransaction();
transaction.Replace(fragmentContainerId,
GetFragmentForPrefString(prefString));
// you'll probably also want to add it to the back stack,
// but it's not strictly necessary I guess.
transaction.Commit();
return true;
}
}
Their sample involves the Java API method getSupportFragmentManager().getFragmentFactory() which doesn't appear to be part of the V28 Xamarin support NuGet packages. But honestly I'm not sure why that level of indirection is necessary; I'd suggest you simply implement something like
switch (prefFragmentName)
{
case "Fragment 1":
return new Fragment1();
// etc
I have a project that I originally developed using Android Studio. I decided to convert it to Xamarin (Visual Studio 2015).
After hours of porting all the code over, everything works except for my Settings activity (PreferenceActivity). I have a few PreferenceFragments that make up the settings, but all of them give me "Unable to instantiate fragment". Here is the exception I am getting:
Java.Lang.RuntimeException: Unable to start activity ComponentInfo{test.mypackagename/md50d00e677e41fc49f8b3c16e79df2b77f.SettingsActivity}: android.app.Fragment$InstantiationException: Unable to instantiate fragment test.mypackagename.GeneralPreferenceFragment: make sure class name exists, is public, and has an empty constructor that is public
I have been looking online for a solution but I just cant seem to find one. Everywhere I look they say make sure there is an empty public constructor, if its an inner class it has to be static. But I have the empty constructor and its not an inner class, its in its own file.
Here is my SettingsActivity.cs:
namespace test.mypackagename
{
public class SettingsActivity : PreferenceActivity
{
protected override void OnPostCreate(Bundle savedInstanceState)
{
base.OnPostCreate(savedInstanceState);
}
public override void OnBuildHeaders(IList<Header> target)
{
LoadHeadersFromResource(Resource.Xml.pref_headers, target);
}
}
}
Here is my GeneralPreferenceFragment.cs:
namespace test.mypackagename
{
public class GeneralPreferenceFragment : PreferenceFragment
{
public GeneralPreferenceFragment() { }
public override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
AddPreferencesFromResource(Resource.Xml.pref_general);
}
}
}
And here is my pref_headers.xml:
<preference-headers xmlns:android="http://schemas.android.com/apk/res/android">
<header android:fragment="test.mypackagename.GeneralPreferenceFragment"
android:title="#string/pref_header_general" />
<header android:fragment="test.mypackagename.OtherPreferenceFragment1"
android:title="#string/pref_header_other1" />
<header android:fragment="test.mypackagename.OtherPreferenceFragment2"
android:title="#string/pref_header_other2" />
<header android:fragment="test.mypackagename.OtherPreferenceFragment3"
android:title="#string/pref_header_other3" />
<header android:fragment="test.mypackagename.OtherPreferenceFragment4"
android:title="#string/pref_header_other4" />
</preference-headers>
This was working fine before so Im not sure what the issue could be. Any help would be much appreciated.
I think you are running into this problem because when not using the [Register] attribute on your PreferenceFragment then its name appended with a MD5 sum by Xamarin.
So in order to actually have the namespace you expect it to in the pref_headers.xml you need to attribute your class:
[Register("test.mypackagename.GeneralPreferenceFragment")]
public class GeneralPreferenceFragment: PreferenceFragment
{
// code here
}
EDIT:
I've just tested the code and it works fine on my machine. I am not using any support packages or anything.
pref_general.xml
<?xml version="1.0" encoding="utf-8" ?>
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android">
<PreferenceCategory
android:title="durr">
<CheckBoxPreference
android:key="checkbox_preference"
android:title="herp"
android:summary="derp" />
</PreferenceCategory>
</PreferenceScreen>
pref_headers.xml
<?xml version="1.0" encoding="utf-8" ?>
<preference-headers xmlns:android="http://schemas.android.com/apk/res/android">
<header android:fragment="test.mypackagename.GeneralPreferenceFragment"
android:title="general" />
</preference-headers>
SettingsActivity.cs
[Activity(Label = "SettingsActivity")]
public class SettingsActivity : PreferenceActivity
{
public override void OnBuildHeaders(IList<Header> target)
{
LoadHeadersFromResource(Resource.Xml.pref_headers, target);
}
}
GeneralPreferenceFragment.cs
[Register("test.mypackagename.GeneralPreferenceFragment")]
public class GeneralPreferenceFragment : PreferenceFragment
{
public override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
AddPreferencesFromResource(Resource.Xml.pref_general);
}
}
This works fine and the app shows up the SettingsActivity with first a general option, after clicking on that it shows up a Title and CheckBox.
This worked even without providing any ctor to the GeneralPreferenceFragment. However, you could try add these:
public GeneralPreferenceFragment()
{
}
public GeneralPreferenceFragment(IntPtr javaRef, JniHandleOwnership transfer)
: base(javaRef, transfer)
{
}
The latter ctor is often needed when the app is coming back from background or when the Java world is invoking the class somehow.
This is the Activity where I'm using the setContentView method. In this app I'm using a xml folder in the res folder and put a prefs.xml file in that.
public class SetWallpaperActivity extends Activity {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//setContentView(android.R.xml.);
//setContentView(R.xml.prefs.xml);
setContentView(android.R.xml.); // This line GENERATE ERROR...
}
This is my prefs.xml file:
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="schemas.android.com/apk/res/android"; >
<CheckBoxPreference android:key="touch" android:title="Enable Touch">/CheckBoxPreference>
<EditTextPreference android:key="numberOfCircles" android:title="Number of Circles"></EditTextPreference>
</PreferenceScreen>
Judging by the xml file you posted as a comment(which has preferences in it) your probably looking to make a settings screen from which the user can set various preferences for your app. If this is the case then you need to extend the PreferenceActivity class instead of the normal Activity:
public class SetWallpaperActivity extends PreferenceActivity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.the_name_of_the_xml_file); // R.xml.prefs.xml from your code
}
}
Also have a look at the official guide regarding this on the android developers site.
You can't set the content view as a xml file from the res/xml folder, because the setContentView needs the id of a layout file(in the form of R.layout.the_layout_file).
I have a sharedPreferences implementation where I store user preferences. When the user clicks "preferences" in menu, I open the standar editor to allow the user to change preferences. This is the code:
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.MENU_PREFERENCES:
Intent intent = new Intent(context, PreferencesActivity.class);
startActivity(intent);
return true;
case xx:
....
}
Toast.makeText(this, "ERROR: Bad menu item", Toast.LENGTH_SHORT).show();
return true;
}
In PreferencesActivity.java I have:
public class PreferencesActivity extends PreferenceActivity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.preferences);
}
}
My question is if can I have a variable in the preferences for internal use, I mean, that will not be showed to user when startActivity(intent)? how?
I suppose I must change something in preferences.xml but dont know exactly what....
thanks
EDIT: as requested, I paste xml:
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" >
<PreferenceCategory>
<ListPreference android:title="Pomodoro duration" android:key="pomodoro_duration" android:summary="Duration of each pomodoro" android:entryValues="#array/array_duration_values" android:defaultValue="25" android:entries="#array/array_duration"/><ListPreference android:key="short_break_duration" android:title="Short break duration" android:summary="Duration of break after each pomodoro" android:entryValues="#array/array_duration_values" android:entries="#array/array_duration" android:defaultValue="5"/>
<ListPreference android:title="Long break interval" android:summary="Interval of the longer break" android:key="intervalos" android:entryValues="#array/interval_array_values" android:defaultValue="4" android:entries="#array/interval_array"/>
<ListPreference android:defaultValue="20" android:title="Long break duration" android:summary="Duration of break after each pomodoro" android:key="long_break_duration" android:entries="#array/array_duration" android:entryValues="#array/array_duration_values"/><RingtonePreference android:title="Notification sound" android:ringtoneType="notification" android:summary="Tone that will sound after pomodoro ends" android:key="notification_tone" android:showDefault="true" android:showSilent="true"/>
<ListPreference android:summary="Period used to calculate productivity" android:title="Calculus period" android:key="calculus_period" android:entryValues="#array/array_calculo_valores" android:entries="#array/array_calculo"/>
</PreferenceCategory>
</PreferenceScreen>
When I inveke the standar UI to allow user to set this variables, I would like to hide one of them programmaticaly. Is possible?
If it is not in preferences.xml, it won't be displayed, so just don't add it there. You can use the SharedPreferences class to get/set preferences without displaying a UI. Something like:
SharedPreferences prefs = PreferenceManager
.getDefaultSharedPreferences(context);
boolean flag = prefs.getBoolean("flag", false);