I'm new in the "Android-App-Dev"-Scene and got one question:
How do I easily make a good clean looking settings page for my app?
There are some kind of headlines and some kind of big buttons on you can tab to go to a new page.
I'm using Android Studio and know how to create a new page, class etc..
As of 2019 the recommended way to do this is to use the AndroidX Preference Library.
PreferenceActivity is actually deprecated in API Level 29 (source):
This class was deprecated in API level 29.
Use the AndroidX Preference Library for consistent behavior across all devices. For more information on using the AndroidX Preference Library see Settings.
For a working minimal example please refer to this example in the official docs.
Use PreferenceActivity
sample code from the developer site:
public class PreferenceWithHeaders extends PreferenceActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Add a button to the header list.
if (hasHeaders()) {
Button button = new Button(this);
button.setText("Some action");
setListFooter(button);
}
}
/**
* Populate the activity with the top-level headers.
*/
#Override
public void onBuildHeaders(List<Header> target) {
loadHeadersFromResource(R.xml.preference_headers, target);
}
/**
* This fragment shows the preferences for the first header.
*/
public static class Prefs1Fragment extends PreferenceFragment {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Make sure default values are applied. In a real app, you would
// want this in a shared function that is used to retrieve the
// SharedPreferences wherever they are needed.
PreferenceManager.setDefaultValues(getActivity(),
R.xml.advanced_preferences, false);
// Load the preferences from an XML resource
addPreferencesFromResource(R.xml.fragmented_preferences);
}
}
/**
* This fragment contains a second-level set of preference that you
* can get to by tapping an item in the first preferences fragment.
*/
public static class Prefs1FragmentInner extends PreferenceFragment {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Can retrieve arguments from preference XML.
Log.i("args", "Arguments: " + getArguments());
// Load the preferences from an XML resource
addPreferencesFromResource(R.xml.fragmented_preferences_inner);
}
}
/**
* This fragment shows the preferences for the second header.
*/
public static class Prefs2Fragment extends PreferenceFragment {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Can retrieve arguments from headers XML.
Log.i("args", "Arguments: " + getArguments());
// Load the preferences from an XML resource
addPreferencesFromResource(R.xml.preference_dependencies);
}
}
}
The preference_headers resource describes the headers to be displayed and the fragments associated with them. It is:
<header android:fragment="com.example.android.apis.preference.PreferenceWithHeaders$Prefs1Fragment"
android:icon="#drawable/ic_settings_applications"
android:title="Prefs 1"
android:summary="An example of some preferences." />
<header android:fragment="com.example.android.apis.preference.PreferenceWithHeaders$Prefs2Fragment"
android:icon="#drawable/ic_settings_display"
android:title="Prefs 2"
android:summary="Some other preferences you can see.">
<!-- Arbitrary key/value pairs can be included with a header as
arguments to its fragment. -->
<extra android:name="someKey" android:value="someHeaderValue" />
</header>
<header android:icon="#drawable/ic_settings_display"
android:title="Intent"
android:summary="Launches an Intent.">
<intent android:action="android.intent.action.VIEW"
android:data="http://www.android.com" />
</header>
The first header is shown by Prefs1Fragment, which populates itself from the following XML resource:
<PreferenceCategory
android:title="#string/inline_preferences">
<CheckBoxPreference
android:key="checkbox_preference"
android:title="#string/title_checkbox_preference"
android:summary="#string/summary_checkbox_preference" />
</PreferenceCategory>
<PreferenceCategory
android:title="#string/dialog_based_preferences">
<EditTextPreference
android:key="edittext_preference"
android:title="#string/title_edittext_preference"
android:summary="#string/summary_edittext_preference"
android:dialogTitle="#string/dialog_title_edittext_preference" />
<ListPreference
android:key="list_preference"
android:title="#string/title_list_preference"
android:summary="#string/summary_list_preference"
android:entries="#array/entries_list_preference"
android:entryValues="#array/entryvalues_list_preference"
android:dialogTitle="#string/dialog_title_list_preference" />
</PreferenceCategory>
<PreferenceCategory
android:title="#string/launch_preferences">
<!-- This PreferenceScreen tag sends the user to a new fragment of
preferences. If running in a large screen, they can be embedded
inside of the overall preferences UI. -->
<PreferenceScreen
android:fragment="com.example.android.apis.preference.PreferenceWithHeaders$Prefs1FragmentInner"
android:title="#string/title_fragment_preference"
android:summary="#string/summary_fragment_preference">
<!-- Arbitrary key/value pairs can be included for fragment arguments -->
<extra android:name="someKey" android:value="somePrefValue" />
</PreferenceScreen>
<!-- This PreferenceScreen tag sends the user to a completely different
activity, switching out of the current preferences UI. -->
<PreferenceScreen
android:title="#string/title_intent_preference"
android:summary="#string/summary_intent_preference">
<intent android:action="android.intent.action.VIEW"
android:data="http://www.android.com" />
</PreferenceScreen>
</PreferenceCategory>
<PreferenceCategory
android:title="#string/preference_attributes">
<CheckBoxPreference
android:key="parent_checkbox_preference"
android:title="#string/title_parent_preference"
android:summary="#string/summary_parent_preference" />
<!-- The visual style of a child is defined by this styled theme attribute. -->
<CheckBoxPreference
android:key="child_checkbox_preference"
android:dependency="parent_checkbox_preference"
android:layout="?android:attr/preferenceLayoutChild"
android:title="#string/title_child_preference"
android:summary="#string/summary_child_preference" />
</PreferenceCategory>
Note that this XML resource contains a preference screen holding another fragment, the Prefs1FragmentInner implemented here. This allows the user to traverse down a hierarchy of preferences; pressing back will pop each fragment off the stack to return to the previous preferences.
See PreferenceFragment for information on implementing the fragments themselves.
Related
Im making wallpaper app which it has a setting button for users which could adjust how many circle can be drawn on the wallpaper. Here i set 5 as default value in the preferences.xml . When i install the app,the wallpaper constructor get the number of circles in preference.xml which just 0 and i have to manually press setting button and set the number. So I want the keep the number is 5 ( default) when installing the app.
Some class that use for the App:
preferences.xml
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<EditTextPreference android:key="numofparticles" android:title="Number of particles" android:summary="Chose a initial particle's number" android:defaultValue="5">
</EditTextPreference>
</PreferenceScreen>
Prefernces.java
public class Preferences extends PreferenceActivity {
public static String numofparticles="numofparticles";
public static String image="getimage";
private static final int Pic_image=1;
private SharedPreferences.OnSharedPreferenceChangeListener PreferenceChangeListener;
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.preferences);
PreferenceChangeListener=new SharedPreferences.OnSharedPreferenceChangeListener() {
#Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String s) {
if (s.equals(numofparticles) && s.getClass().getSimpleName()=="Interger")
{
Preference numofP=findPreference(s);
numofP.setSummary(sharedPreferences.getString(s,"")+ " Particles");
Toast.makeText(getApplicationContext(),"success",Toast.LENGTH_SHORT);
}
}
};
Snippet from another class which i get this preference data.
public wallpaperengine()
{
display.getRealSize(size);
wallpaper_height=size.y;
wallpaper_width=size.x;
background=BitmapFactory.decodeResource(getResources(),R.drawable.particlebackground);
background=Bitmap.createScaledBitmap(background,wallpaper_width,wallpaper_height,true);
sharedPreferences= PreferenceManager.getDefaultSharedPreferences(particlewallpaper.this);
sharedPreferences.registerOnSharedPreferenceChangeListener(listener);
num_particle=Integer.valueOf(sharedPreferences.getString(Preferences.numofparticles,"5"));
handler.post(drawFrames);
}
The default 5 is in the layout file (which resides in xml folder of AS) for your preferences activity.
Not in the real preferences data file which resides in /data/data/package/shared_prefs/package_preferences.xml.
The wallpaper code reads from the real data file.
Not from the layout file.
And you know that that activity and layout file are not needed 'to work with shared preferences'.
I'm trying to implement a shared element transition between a fragment that has a Recyclerview and another fragment let say something like a Detail Fragment.
The fragments are:
<fragment
android:id="#+id/nav_staff"
android:name="main.staff.StaffFragment"
android:label="#string/menu_staff"
tools:layout="#layout/fragment_staff">
<action
android:id="#+id/action_nav_staff_to_nav_user_profile"
app:destination="#id/nav_user_profile">
<argument
android:name="selectedUser"
app:argType="string" />
</action>
</fragment>
<fragment
android:id="#+id/nav_user_profile"
android:name="main.staff.UserProfileFragment"
android:label="#string/profile"
tools:layout="#layout/fragment_user_profile" />
In the recycler view adapter I'm setting the element transition name like:
public void setBinding(UserModel model, OnItemClicked onUserClick) {
binding.setModel(model);
binding.executePendingBindings();
binding.coworkerImage.setTransitionName(model.getUid());
binding.itemContainer.setOnClickListener(v -> onUserClick.onItemClick(binding.coworkerImage, model.getUid()));
}
Then in the detail fragment in the onCreate method I'm setting:
setSharedElementEnterTransition(TransitionInflater.from(getContext()).inflateTransition(android.R.transition.move));
then in the onViewCreated method of the detail fragment I'm setting the transition name of the image like:
userViewModel.getCurrentUser(selectedUid).observe(getViewLifecycleOwner(), model -> {
USER = model;
binding.userAvatar.setTransitionName(USER.getUid());
binding.setModel(USER);
});
In the main fragment when I click an item of the recycler view the transition animation is not happening, I did everything according to the documentation:
private void onItemClick(View v, String uid) {
FragmentNavigator.Extras extras = new FragmentNavigator.Extras.Builder()
.addSharedElement(v, v.getTransitionName())
.build();
StaffFragmentDirections.ActionNavStaffToNavUserProfile action =
StaffFragmentDirections.actionNavStaffToNavUserProfile(uid);
navController.navigate(action, extras);
}
The idea is: when I click an item in the recycler view it should open the detail fragment with shared element transition.
I have a list objects in a Recyclerview. When long-pressing an item I want to show a dialog with data from the item clicked.
The Recyclerview is using data binding for each item and I am able to display data from the selected item using Log when long-pressing.
When trying to show a dialog, however, you need to get to the Activity, which is not recommended to use in the ViewModel object.
So how can I show the dialog?
Thanks, Ove
Conceptually a ViewModel strikes me as the wrong place to launch a Dialog from. To do it more cleanly I would pass the RecyclerView.ViewHolder into the layout, and have a method on the ViewHolder that triggers a custom listener on your RecyclerView.Adapter. Then whoever subscribes to that listener (Activity/Fragment) can launch the Dialog. May seem a little roundabout, but I don't think a ViewModel of a list item should have knowledge or control of its environment.
Here is an example. This is a general pattern for handling RecyclerView item clicks with data binding and a ViewModel. This is not a complete example, just the code to highlight this specific pattern.
Layout:
<layout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
>
<data>
<variable
name="viewHolder"
type="com.example.ViewHolder"
/>
<variable
name="viewModel"
type="com.example.ViewModel"
/>
</data>
<com.example.View
android:layout_width="match_parent"
android:layout_height="24dp"
android:onClick="#{() -> viewHolder.onClick(viewModel)}"
/>
</layout>
Adapter:
class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {
public interface SelectionListener {
void onSelectionChanged(int newPosition, ViewModel viewModel);
}
private #NonNull WeakReference<SelectionListener> selectionListener =
new WeakReference<>(null);
public void setSelectionListener(#Nullable SelectionListener listener) {
selectionListener = new WeakReference<>(listener);
}
public class ViewHolder extends RecyclerView.ViewHolder<ViewBinding> {
ViewHolder(ViewBinding binding) {
super(binding.getRoot());
binding.setViewHolder(this);
binding.setViewModel(new ViewModel());
}
public void onClick(ViewModel viewModel) {
SelectionListener listener = selectionListener.get();
if (listener != null) {
listener.onSelectionChanged(getAdapterPosition(), viewModel);
}
}
}
}
See the Variables section of the official documentation of the Data Binding Library. There you find a variable context you can use.
A special variable named context is generated for use in binding expressions as needed. The value for context is the Context from the root View's getContext(). The context variable will be overridden by an explicit variable declaration with that name.
Basically you could just pass it to another variable like the viewModel to show the dialog from there.
android:onClick="#{v -> viewModel.showDialog(context)}"
I think using a binding adapter for a recyclerview and put the adapter n ViewModel, then make the viewmodel is the model of fragment and passing adapter for the setAdapter method in xml itself.
So you can use the context of item for example itemView.getContext() to show AlertDialog
The hint from Bayoudh led me in the right direction, but I'm posting this to put the pieces together. Below is a cardview that is clickable. Since my ViewModel holds no reference to the activity we have to pass the view in question as a parameter.
<android.support.v7.widget.CardView
android:id="#+id/cardviewContact"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="#dimen/text_margin_0.5x"
android:layout_marginRight="#dimen/text_margin_0.5x"
android:layout_marginTop="#dimen/text_margin_0.5x"
android:background="?attr/selectableItemBackground"
android:clickable="true"
android:minHeight="50dp"
card_view:cardCornerRadius="4dp"
android:onClick="#{(view) -> viewModel.onClick(view)}" >
The android:onClick="#{(view) -> viewModel.onClick(view)}" statement takes the current view as a parameter so you can use it in the ViewModel to get context with view.getContext() as Bayoudh states.
My preference values aren't saving and everytime I read the SharedPreferences file it returns default values; changes are supposed to be automatically handled by the system when a user makes a choice (like selecting an option in a ListPreference) so I don't know why this isn't happening in my app.
According to google
"When the user changes a setting, the system updates the corresponding value in the SharedPreferences file for you. The only time you should directly interact with the associated SharedPreferences file is when you need to read the value in order to determine your app's behavior based on the user's setting."
Should I be manipulating a SharedPreference.Editor instance in my OnSharedPreferenceChangeListener or is there something else I need to do to make values of user settings persist?
The Problem in Code: As a result of this misunderstanding, my code doesn't persist user settings values (the default value is always chosen when I read the SharedPreference file in my MainActivity's OnCreate). As it is now, the buttons chosen in my Preferences menu View persist upon app restarts, but it seems that choosing an option in this menu doesn't save key values to the SharedPreferences file.
What do I need to do to make the values set by the user in my ListPreference persist?
MainActivity
public class MainActivity extends FragmentActivity {
private static int prefWoodColor; //saved pref variable for OpenGL neck texture
private SharedPreferences settings;
#Override
protected void onCreate(Bundle savedInstanceState) {
...
//Restore preferences
settings = PreferenceManager.getDefaultSharedPreferences(this);
prefWoodColor = Integer.parseInt(settings.getString(this.KEY_PREF_WOOD_TYPE, "Maple"));
...
}
}
Preferences Activity
public class FragmentSettingsMenu extends com.takisoft.fix.support.v7.preference.PreferenceFragmentCompat {
private SharedPreferences.OnSharedPreferenceChangeListener listener;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Load the preferences from the XML resource
addPreferencesFromResource(R.xml.preferences);
listener = new SharedPreferences.OnSharedPreferenceChangeListener() {
public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
if (key.equals("pref_wood")) {
Preference woodPref = findPreference(key);
String color = woodPref.getSharedPreferences().getString(key, "Maple");
//Should I be calling edit.apply() logic here?
}
}
};
}
...
}
Preferences.xml
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
android:title="Settings"
<ListPreference
android:key="pref_wood"
android:title="#string/pref_wood"
android:dialogTitle="#string/pref_wood"
android:entries="#array/pref_wood_entries"
android:entryValues="#array/pref_wood_values"
android:defaultValue="#string/pref_wood_default" />
</PreferenceScreen>
Strings.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">MyApp</string>
<string name="dummy_button">Dummy Button</string>
<string name="dummy_content">DUMMY\nCONTENT</string>
<!--Preference Menu Strings-->
<string name="pref_wood">Wood Style</string>
<string-array name="pref_wood_entries">
<item>"Maple"</item>
<item>"Cedar"</item>
<item>"Oak"</item>
</string-array>
<string-array name="pref_wood_values">
<item >0</item>
<item >1</item>
<item >2</item>
</string-array>
<string name="pref_wood_default">Maple</string>
</resources>
The system saves changes automatically!
I'm learning how to use Preferences in Android. I have a small a application on which I want to set a series of preferences which includes a RingTonePreference. My activity implements OnSharedPreferenceChangeListener and override the onSharedPreferenceChanged(SharedPreferences sharedPreferences,String key) method to handle all the different preferences.
Until the ringtone one everything was working fine. My problem seems to be that the key of the ringTonePreference is not found and my code never enters the if statement of the ringTonePreference.
Here is my onSharedPreferenceChanged method:
public class preferenceActivity extends PreferenceActivity implements
OnSharedPreferenceChangeListener {
// code here
#Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
String key) {
if (key.equals("list_subject_pref")) {
// do something
} else if (key.equals("et_subject_pref")) {
// do something
} else if (key.equals("list_times_pref")) {
// do something
// here is problem, it never enters the if statement why?
} else if (key.equals("ringtonePref")) {
RingtonePreference preference = (RingtonePreference) findPreference("ringtonePref");
preference.setSummary("ringtone selected");
}
}
}
And here is my preferences.xml
<?xml version="1.0" encoding="utf-8"?>
<PreferenceCategory android:title="#string/subjectpreferences" >
<ListPreference
android:key="list_subject_pref"
// other stuff here/>
<EditTextPreference
android:defaultValue=""
android:key="et_subject_pref"
// other stuff here />
<MultiSelectListPreference
android:key="list_times_pref"
// other stuff here />
</PreferenceCategory>
<PreferenceCategory android:title="#string/reminderpreferences" >
<RingtonePreference
android:key="ringtonePref"
android:showDefault="true"
android:showSilent="true"
android:summary="Select a ringtone"
android:title="Ringtones" />
<CheckBoxPreference
android:defaultValue="true"
android:key="preference_ringtonereminder"
android:summary="summary"
android:summaryOff="#string/remindme_off"
android:summaryOn="#string/remindme_on"
android:title="#string/reminderringtone_title" />
</PreferenceCategory>
I have seen this question but I'm still not able to fix my problem.