basically what I want is this:
-> I have some settings that (of course) can be modified by the user, on of it is the 'number of cubes'
-> There is an other setting which depends on this setting (movement)
----> if there is one cube the setting is disabled (this works)
----> if there are two cubes there are two options for movement and the setting is enabled (this works too)
----> if there are four cubes there needs to be a choice added for movement and here lays my problem:
I can programmatically change the value of the ListPreference to add this setting but:
-> when the user sets the added value "paired"
and
-> (s)he moves away from the settings, the setting is read correctly
however
-> when the users moves back to the settings the setting is set to the first element of the list(synchronized), not being the choice (s)he made earlier
-> the setting (paired) is remembered by the SharedPreferences instance which I get by calling:
PreferenceManager.getDefaultSharedPreferences(context);
but when moving to the settings again shows the wrong value for the setting (which nobody wants)
How do I persist a programmatically added value?
Of course the reason is that I read the preferences.xml again when the Activity is resumed, but I don't know how to persist the choice made by the user when the Activity is recreated.
This is my code: (the two methods that matter)
public class SettingsActivity extends PreferenceActivity implements
SharedPreferences.OnSharedPreferenceChangeListener {
#Override
public void onCreate(Bundle savedInstanceState) {
Log.d("SA", "onCreate");
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.preferences);
updateLists();
}
private void updateLists() {
Log.d("SA", "updateLists");
Preference numberOfCubesPref = findPreference("numberOfCubes");
Preference tupleTypePref = findPreference("tuple");
Preference movementTypePref = findPreference("movement_type");
Preference pictureDistributionPref = findPreference("distribution_of_pictures");
ListPreference numberOfCubesListPref = (ListPreference) numberOfCubesPref;
if(numberOfCubesListPref.getEntry() == null){
numberOfCubesListPref.setValueIndex(0);
}
numberOfCubesPref.setSummary(numberOfCubesListPref.getEntry());
ListPreference movementTypeListPref = (ListPreference) movementTypePref;
if(movementTypeListPref.getEntry() == null){
movementTypeListPref.setValueIndex(0);
}
if (numberOfCubesListPref.getEntry().equals("Four")) {
movementTypePref.setEnabled(true);
pictureDistributionPref.setEnabled(true);
CharSequence[] oldEntries = movementTypeListPref.getEntries();
if (oldEntries.length == 2) {
Log.d("SA","length is twoo");
CharSequence[] newEntries = new CharSequence[oldEntries.length + 1];
newEntries[0] = oldEntries[0];
newEntries[1] = "Paired";
newEntries[2] = oldEntries[1];
movementTypeListPref.setEntries(newEntries);
CharSequence[] oldEntryValues = movementTypeListPref
.getEntryValues();
CharSequence[] newEntryValues = new CharSequence[oldEntryValues.length + 1];
newEntryValues[0] = oldEntryValues[0];
newEntryValues[1] = "Paired";
newEntryValues[2] = oldEntryValues[1];
movementTypeListPref.setEntryValues(newEntryValues);
}
} else if (numberOfCubesListPref.getEntry().equals("Two")) {
movementTypePref.setEnabled(true);
pictureDistributionPref.setEnabled(true);
CharSequence[] oldEntries = movementTypeListPref.getEntries();
if (oldEntries.length == 3) {
CharSequence[] newEntries = new CharSequence[oldEntries.length - 1];
newEntries[0] = oldEntries[0];
newEntries[1] = oldEntries[2];
movementTypeListPref.setEntries(newEntries);
CharSequence[] oldEntryValues = movementTypeListPref
.getEntryValues();
CharSequence[] newEntryValues = new CharSequence[oldEntryValues.length - 1];
newEntryValues[0] = oldEntryValues[0];
newEntryValues[1] = oldEntryValues[2];
movementTypeListPref.setEntryValues(newEntryValues);
}
} else {
movementTypePref.setEnabled(false);
pictureDistributionPref.setEnabled(false);
}
ListPreference pictureDistributionListPref = (ListPreference) pictureDistributionPref;
ListPreference tupleTypeListPref = (ListPreference) tupleTypePref;
if(tupleTypeListPref.getEntry() == null){
tupleTypeListPref.setValueIndex(0);
}
CharSequence[] entries = pictureDistributionListPref.getEntries();
CharSequence target, replacement;
if (tupleTypeListPref.getEntry().equals("Two of the same kind")) {
target = "Triplet";
replacement = "Pair";
} else {
target = "Pair";
replacement = "Triplet";
}
for (int i = 0; i < entries.length; i++) {
entries[i] = ((String) entries[i]).replace(target, replacement);
}
pictureDistributionListPref.setEntries(entries);
if(pictureDistributionListPref.getEntry() == null){
pictureDistributionListPref.setValueIndex(0);
}
tupleTypePref.setSummary(tupleTypeListPref.getEntry());
movementTypePref.setSummary(movementTypeListPref.getEntry());
pictureDistributionPref.setSummary(pictureDistributionListPref.getEntry());
}
and my preferences.xml (relevant piece):
<ListPreference
android:dialogTitle="#string/choose_movement_type"
android:enabled="false"
android:entries="#array/movement_type_entries"
android:entryValues="#array/movement_type_values"
android:key="movement_type"
android:title="#string/movement_type" />
and strings.xml: (relevant piece)
<string name="movement_type">Movement type</string>
<string name="choose_movement_type">How do you want to control the cubes?</string>
<string-array name="movement_type_entries">
<item>Synchronized</item>
<item>Independent</item>
</string-array>
<string-array name="movement_type_values">
<item>Synchronized</item>
<item>Independent</item>
</string-array>
The activity is called from within an other activity like this:
settings.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
Intent myIntent = new Intent(MainActivity.this,
SettingsActivity.class);
MainActivity.this.startActivity(myIntent);
}
});
any help/comments/tips are welcome :)
S.
it looks like you are modifying the preference temporary. to save the prefrences and be sure it remains the same, use sharedPreferencesTurbo
Related
I have implemented the new architecture component on my android app, but unfortunately handling the states of these fragments has become a nightmare for me. Whenever I press the icon of the fragment, the fragment is recreated every time I navigate. How can I handle this or rather save these fragments states?
Here is my main activity handling the five fragments:
public class MainActivityCenterofInformation extends AppCompatActivity {
BottomNavigationView bottomNavigationView;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate (savedInstanceState);
setContentView (R.layout.activity_maincict);
setUpNavigation ();
}
public void setUpNavigation(){
bottomNavigationView = findViewById (R.id.bottom_nav_cict);
NavHostFragment navHostFragment = (NavHostFragment)getSupportFragmentManager ()
.findFragmentById (R.id.nav_host_fragment_cict);
NavigationUI.setupWithNavController (bottomNavigationView, navHostFragment.getNavController ());
}
//adding animations to the fragment
}
I can't read Kotlin so please direct me to Java, thanks.
TL;DR: skip to the JUST SHOW ME THE STEPS ALREADY !!! section
That is the normal behaviour of the fragments. They are suppose to be recreated every time they are removed or replaced and you are suppose to restore their states using onSaveInstanceState.
Here is a nice article that describes how to do it : Saving Fragment States
Other than that you can use View Model which is the part of the following recommended android architecture. They are a great way to retain and restore UI data.
You can learn how to implement this architecture by following this step by step code lab
EDIT : Solution
Note : The solution assumes one doesn't wants to use ViewModels and simply wants to hide or show fragments using the Navigation UI
It covers the following points
Keeping fragment alive during navigation
Implement custom navigation behavior on back key press (Optional)
Background :
Android Navigation component has a NavController class that you can use to navigate to different destinations. NavController uses a Navigator that actually does the navigation. Navigator is an abstract class and anyone can extend/inherit it to provide custom navigation depending on the type of destination. When using fragments as destinations the NavHostFragment uses a FragmentNavigator whose default implementation replaces the fragments whenever we navigate using FragmentTransaction.replace() which completely destroys the previous fragment and adds a new fragment. So we have to create our own navigator and instead of using FragmentTransaction.replace() we will use a combination of FragmentTransaction.hide() and FragmentTransaction.show() to avoid fragments from being destroyed.
Default behavior of Navigation UI :
By default whenever you navigate to any other fragment other than the home fragment they won't get added to backstack so lets say if you select fragments in the following order
A -> B -> C -> D -> E
your back stack will have only
[A, E]
as you can see the fragments B, C, D weren't added to backstack so pressing back press will always get you to fragment A which is the home fragment
The behavior we want for now :
We want a simple yet effective behavior. We wan't all fragments to get added to backstack but if the fragment is already in backstack we want to pop all fragments upto the selected fragment.
Lets say I select fragment in following order
A -> B -> C -> D -> E
the backstack should also be
[A, B, C, D, E]
upon pressing back only the last fragment should be popped and backstack should be like this
[A, B, C, D]
but if we navigate to lets say fragment B, since B is already in the stack then all the fragments above B should be popped and our backstack should look like this
[A, B]
I hope this behavior makes sense. This behavior is easy to implement using global actions as you will see below and is better than the default one.
OK Hotshot! now what ? :
Now we have two options
extend FragmentNavigator
copy/paste FragmentNavigator
Well I personally wanted to just extend FragmentNavigator and override navigate() method but since all its member variables are private I couldn't implement proper navigation.
So I decided to copy paste the entire FragmentNavigator class and just change the name in entire code from "FragmentNavigator" to whatever I want to call it.
JUST SHOW ME THE STEPS ALREADY !!! :
Create custom navigator
Use custom tag
Add global actions
Use global actions
Add the custom navigator to the NavController
STEP 1: Create custom navigator
Here is my custom navigator called StickyCustomNavigator. All the code is same as FragmentNavigator except the navigate() method. As you can see it uses hide() , show() and add() method instead of replace(). The logic is simple. Hide the previous fragment and show the destination fragment. If this is our first time going to a specific destination fragment then add the fragment instead of showing it.
#Navigator.Name("sticky_fragment")
public class StickyFragmentNavigator extends Navigator<StickyFragmentNavigator.Destination> {
private static final String TAG = "StickyFragmentNavigator";
private static final String KEY_BACK_STACK_IDS = "androidx-nav-fragment:navigator:backStackIds";
private final Context mContext;
#SuppressWarnings("WeakerAccess") /* synthetic access */
final FragmentManager mFragmentManager;
private final int mContainerId;
#SuppressWarnings("WeakerAccess") /* synthetic access */
ArrayDeque<Integer> mBackStack = new ArrayDeque<>();
#SuppressWarnings("WeakerAccess") /* synthetic access */
boolean mIsPendingBackStackOperation = false;
private final FragmentManager.OnBackStackChangedListener mOnBackStackChangedListener =
new FragmentManager.OnBackStackChangedListener() {
#SuppressLint("RestrictedApi")
#Override
public void onBackStackChanged() {
// If we have pending operations made by us then consume this change, otherwise
// detect a pop in the back stack to dispatch callback.
if (mIsPendingBackStackOperation) {
mIsPendingBackStackOperation = !isBackStackEqual();
return;
}
// The initial Fragment won't be on the back stack, so the
// real count of destinations is the back stack entry count + 1
int newCount = mFragmentManager.getBackStackEntryCount() + 1;
if (newCount < mBackStack.size()) {
// Handle cases where the user hit the system back button
while (mBackStack.size() > newCount) {
mBackStack.removeLast();
}
dispatchOnNavigatorBackPress();
}
}
};
public StickyFragmentNavigator(#NonNull Context context, #NonNull FragmentManager manager,
int containerId) {
mContext = context;
mFragmentManager = manager;
mContainerId = containerId;
}
#Override
protected void onBackPressAdded() {
mFragmentManager.addOnBackStackChangedListener(mOnBackStackChangedListener);
}
#Override
protected void onBackPressRemoved() {
mFragmentManager.removeOnBackStackChangedListener(mOnBackStackChangedListener);
}
#Override
public boolean popBackStack() {
if (mBackStack.isEmpty()) {
return false;
}
if (mFragmentManager.isStateSaved()) {
Log.i(TAG, "Ignoring popBackStack() call: FragmentManager has already"
+ " saved its state");
return false;
}
if (mFragmentManager.getBackStackEntryCount() > 0) {
mFragmentManager.popBackStack(
generateBackStackName(mBackStack.size(), mBackStack.peekLast()),
FragmentManager.POP_BACK_STACK_INCLUSIVE);
mIsPendingBackStackOperation = true;
} // else, we're on the first Fragment, so there's nothing to pop from FragmentManager
mBackStack.removeLast();
return true;
}
#NonNull
#Override
public StickyFragmentNavigator.Destination createDestination() {
return new StickyFragmentNavigator.Destination(this);
}
#NonNull
public Fragment instantiateFragment(#NonNull Context context,
#SuppressWarnings("unused") #NonNull FragmentManager fragmentManager,
#NonNull String className, #Nullable Bundle args) {
return Fragment.instantiate(context, className, args);
}
#Nullable
#Override
public NavDestination navigate(#NonNull StickyFragmentNavigator.Destination destination, #Nullable Bundle args,
#Nullable NavOptions navOptions, #Nullable Navigator.Extras navigatorExtras) {
if (mFragmentManager.isStateSaved()) {
Log.i(TAG, "Ignoring navigate() call: FragmentManager has already"
+ " saved its state");
return null;
}
String className = destination.getClassName();
if (className.charAt(0) == '.') {
className = mContext.getPackageName() + className;
}
final FragmentTransaction ft = mFragmentManager.beginTransaction();
int enterAnim = navOptions != null ? navOptions.getEnterAnim() : -1;
int exitAnim = navOptions != null ? navOptions.getExitAnim() : -1;
int popEnterAnim = navOptions != null ? navOptions.getPopEnterAnim() : -1;
int popExitAnim = navOptions != null ? navOptions.getPopExitAnim() : -1;
if (enterAnim != -1 || exitAnim != -1 || popEnterAnim != -1 || popExitAnim != -1) {
enterAnim = enterAnim != -1 ? enterAnim : 0;
exitAnim = exitAnim != -1 ? exitAnim : 0;
popEnterAnim = popEnterAnim != -1 ? popEnterAnim : 0;
popExitAnim = popExitAnim != -1 ? popExitAnim : 0;
ft.setCustomAnimations(enterAnim, exitAnim, popEnterAnim, popExitAnim);
}
String tag = Integer.toString(destination.getId());
Fragment primaryNavigationFragment = mFragmentManager.getPrimaryNavigationFragment();
if(primaryNavigationFragment != null)
ft.hide(primaryNavigationFragment);
Fragment destinationFragment = mFragmentManager.findFragmentByTag(tag);
if(destinationFragment == null) {
destinationFragment = instantiateFragment(mContext, mFragmentManager, className, args);
destinationFragment.setArguments(args);
ft.add(mContainerId, destinationFragment , tag);
}
else
ft.show(destinationFragment);
ft.setPrimaryNavigationFragment(destinationFragment);
final #IdRes int destId = destination.getId();
final boolean initialNavigation = mBackStack.isEmpty();
// TODO Build first class singleTop behavior for fragments
final boolean isSingleTopReplacement = navOptions != null && !initialNavigation
&& navOptions.shouldLaunchSingleTop()
&& mBackStack.peekLast() == destId;
boolean isAdded;
if (initialNavigation) {
isAdded = true;
} else if (isSingleTopReplacement) {
// Single Top means we only want one instance on the back stack
if (mBackStack.size() > 1) {
// If the Fragment to be replaced is on the FragmentManager's
// back stack, a simple replace() isn't enough so we
// remove it from the back stack and put our replacement
// on the back stack in its place
mFragmentManager.popBackStackImmediate(
generateBackStackName(mBackStack.size(), mBackStack.peekLast()), 0);
mIsPendingBackStackOperation = false;
}
isAdded = false;
} else {
ft.addToBackStack(generateBackStackName(mBackStack.size() + 1, destId));
mIsPendingBackStackOperation = true;
isAdded = true;
}
if (navigatorExtras instanceof FragmentNavigator.Extras) {
FragmentNavigator.Extras extras = (FragmentNavigator.Extras) navigatorExtras;
for (Map.Entry<View, String> sharedElement : extras.getSharedElements().entrySet()) {
ft.addSharedElement(sharedElement.getKey(), sharedElement.getValue());
}
}
ft.setReorderingAllowed(true);
ft.commit();
// The commit succeeded, update our view of the world
if (isAdded) {
mBackStack.add(destId);
return destination;
} else {
return null;
}
}
#Override
#Nullable
public Bundle onSaveState() {
Bundle b = new Bundle();
int[] backStack = new int[mBackStack.size()];
int index = 0;
for (Integer id : mBackStack) {
backStack[index++] = id;
}
b.putIntArray(KEY_BACK_STACK_IDS, backStack);
return b;
}
#Override
public void onRestoreState(#Nullable Bundle savedState) {
if (savedState != null) {
int[] backStack = savedState.getIntArray(KEY_BACK_STACK_IDS);
if (backStack != null) {
mBackStack.clear();
for (int destId : backStack) {
mBackStack.add(destId);
}
}
}
}
#NonNull
private String generateBackStackName(int backStackIndex, int destId) {
return backStackIndex + "-" + destId;
}
private int getDestId(#Nullable String backStackName) {
String[] split = backStackName != null ? backStackName.split("-") : new String[0];
if (split.length != 2) {
throw new IllegalStateException("Invalid back stack entry on the "
+ "NavHostFragment's back stack - use getChildFragmentManager() "
+ "if you need to do custom FragmentTransactions from within "
+ "Fragments created via your navigation graph.");
}
try {
// Just make sure the backStackIndex is correctly formatted
Integer.parseInt(split[0]);
return Integer.parseInt(split[1]);
} catch (NumberFormatException e) {
throw new IllegalStateException("Invalid back stack entry on the "
+ "NavHostFragment's back stack - use getChildFragmentManager() "
+ "if you need to do custom FragmentTransactions from within "
+ "Fragments created via your navigation graph.");
}
}
#SuppressWarnings("WeakerAccess") /* synthetic access */
boolean isBackStackEqual() {
int fragmentBackStackCount = mFragmentManager.getBackStackEntryCount();
// Initial fragment won't be on the FragmentManager's back stack so +1 its count.
if (mBackStack.size() != fragmentBackStackCount + 1) {
return false;
}
// From top to bottom verify destination ids match in both back stacks/
Iterator<Integer> backStackIterator = mBackStack.descendingIterator();
int fragmentBackStackIndex = fragmentBackStackCount - 1;
while (backStackIterator.hasNext() && fragmentBackStackIndex >= 0) {
int destId = backStackIterator.next();
try {
int fragmentDestId = getDestId(mFragmentManager
.getBackStackEntryAt(fragmentBackStackIndex--)
.getName());
if (destId != fragmentDestId) {
return false;
}
} catch (NumberFormatException e) {
throw new IllegalStateException("Invalid back stack entry on the "
+ "NavHostFragment's back stack - use getChildFragmentManager() "
+ "if you need to do custom FragmentTransactions from within "
+ "Fragments created via your navigation graph.");
}
}
return true;
}
#NavDestination.ClassType(Fragment.class)
public static class Destination extends NavDestination {
private String mClassName;
public Destination(#NonNull NavigatorProvider navigatorProvider) {
this(navigatorProvider.getNavigator(StickyFragmentNavigator.class));
}
public Destination(#NonNull Navigator<? extends StickyFragmentNavigator.Destination> fragmentNavigator) {
super(fragmentNavigator);
}
#CallSuper
#Override
public void onInflate(#NonNull Context context, #NonNull AttributeSet attrs) {
super.onInflate(context, attrs);
TypedArray a = context.getResources().obtainAttributes(attrs,
R.styleable.FragmentNavigator);
String className = a.getString(R.styleable.FragmentNavigator_android_name);
if (className != null) {
setClassName(className);
}
a.recycle();
}
#NonNull
public final StickyFragmentNavigator.Destination setClassName(#NonNull String className) {
mClassName = className;
return this;
}
#NonNull
public final String getClassName() {
if (mClassName == null) {
throw new IllegalStateException("Fragment class was not set");
}
return mClassName;
}
}
public static final class Extras implements Navigator.Extras {
private final LinkedHashMap<View, String> mSharedElements = new LinkedHashMap<>();
Extras(Map<View, String> sharedElements) {
mSharedElements.putAll(sharedElements);
}
#NonNull
public Map<View, String> getSharedElements() {
return Collections.unmodifiableMap(mSharedElements);
}
public static final class Builder {
private final LinkedHashMap<View, String> mSharedElements = new LinkedHashMap<>();
#NonNull
public StickyFragmentNavigator.Extras.Builder addSharedElements(#NonNull Map<View, String> sharedElements) {
for (Map.Entry<View, String> sharedElement : sharedElements.entrySet()) {
View view = sharedElement.getKey();
String name = sharedElement.getValue();
if (view != null && name != null) {
addSharedElement(view, name);
}
}
return this;
}
#NonNull
public StickyFragmentNavigator.Extras.Builder addSharedElement(#NonNull View sharedElement, #NonNull String name) {
mSharedElements.put(sharedElement, name);
return this;
}
#NonNull
public StickyFragmentNavigator.Extras build() {
return new StickyFragmentNavigator.Extras(mSharedElements);
}
}
}
}
STEP 2: Use custom tag
Now open up your navigation.xml file and rename the fragment tags related to your bottom navigation with whatever name you gave in #Navigator.Name() earlier.
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/mobile_navigation"
app:startDestination="#+id/navigation_home">
<sticky_fragment
android:id="#+id/navigation_home"
android:name="com.example.bottomnavigationlogic.ui.home.HomeFragment"
android:label="#string/title_home"
tools:layout="#layout/fragment_home" />
</navigation>
STEP 3: Add global action
Global actions are a way to navigate to destination from anywhere in your app. You can use the visual editor or directly use xml to add global actions. Set global action on each fragment with the following settings
destination : self
popUpTo : self
singleTop : true/checked
This your how your navigation.xml should look like after adding global actions
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/mobile_navigation"
app:startDestination="#+id/navigation_home">
<sticky_fragment
android:id="#+id/navigation_home"
android:name="com.example.bottomnavigationlogic.ui.home.HomeFragment"
android:label="#string/title_home"
tools:layout="#layout/fragment_home" />
<sticky_fragment
android:id="#+id/navigation_images"
android:name="com.example.bottomnavigationlogic.ui.images.ImagesFragment"
android:label="#string/title_images"
tools:layout="#layout/fragment_images" />
<sticky_fragment
android:id="#+id/navigation_videos"
android:name="com.example.bottomnavigationlogic.ui.videos.VideosFragment"
android:label="#string/title_videos"
tools:layout="#layout/fragment_videos" />
<sticky_fragment
android:id="#+id/navigation_songs"
android:name="com.example.bottomnavigationlogic.ui.songs.SongsFragment"
android:label="#string/title_songs"
tools:layout="#layout/fragment_songs" />
<sticky_fragment
android:id="#+id/navigation_notifications"
android:name="com.example.bottomnavigationlogic.ui.notifications.NotificationsFragment"
android:label="#string/title_notifications"
tools:layout="#layout/fragment_notifications" />
<action
android:id="#+id/action_global_navigation_home"
app:destination="#id/navigation_home"
app:launchSingleTop="true"
app:popUpTo="#id/navigation_home" />
<action
android:id="#+id/action_global_navigation_notifications"
app:destination="#id/navigation_notifications"
app:launchSingleTop="true"
app:popUpTo="#id/navigation_notifications" />
<action
android:id="#+id/action_global_navigation_songs"
app:destination="#id/navigation_songs"
app:launchSingleTop="true"
app:popUpTo="#id/navigation_songs" />
<action
android:id="#+id/action_global_navigation_videos"
app:destination="#id/navigation_videos"
app:launchSingleTop="true"
app:popUpTo="#id/navigation_videos" />
</navigation>
STEP 4: Use global actions
When you wrote
NavigationUI.setupWithNavController (bottomNavigationView, navHostFragment.getNavController ());
then inside setupWithNavController() NavigationUI uses bottomNavigationView.setOnNavigationItemSelectedListener() to navigate to proper fragments depending on id of the menu item that was clicked. It's default behavior is as I mentioned before. We will add our own implementation to it and use global actions to achieve our desired back press behavior.
Here is how you do it simply in MainActivity
bottomNavigationView.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() {
#Override
public boolean onNavigationItemSelected(#NonNull MenuItem menuItem) {
int id = menuItem.getItemId();
if (menuItem.isChecked()) return false;
switch (id)
{
case R.id.navigation_home :
navController.navigate(R.id.action_global_navigation_home);
break;
case R.id.navigation_images :
navController.navigate(R.id.action_global_navigation_images);
break;
case R.id.navigation_videos :
navController.navigate(R.id.action_global_navigation_videos);
break;
case R.id.navigation_songs :
navController.navigate(R.id.action_global_navigation_songs);
break;
case R.id.navigation_notifications :
navController.navigate(R.id.action_global_navigation_notifications);
break;
}
return true;
}
});
FINAL STEP 5: Add your custom navigator to NavController
Add your navigator as follow in your MainActivity. Make sure you are passing childFragmentManager of the NavHostFragment.
navController.getNavigatorProvider().addNavigator(new StickyFragmentNavigator(this, navHostFragment.getChildFragmentManager(),R.id.nav_host_fragment));
Also add the navigation graph to NavController here as well using setGraph() method as shown below.
This is how my MainActivity looks like after step 4 and step 5
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
BottomNavigationView navView = findViewById(R.id.nav_view);
AppBarConfiguration appBarConfiguration = new AppBarConfiguration.Builder(
R.id.navigation_home, R.id.navigation_images, R.id.navigation_videos,R.id.navigation_songs,R.id.navigation_notifications)
.build();
NavHostFragment navHostFragment = (NavHostFragment)getSupportFragmentManager().findFragmentById(R.id.nav_host_fragment);
final NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment);
navController.getNavigatorProvider().addNavigator(new StickyFragmentNavigator(this, navHostFragment.getChildFragmentManager(),R.id.nav_host_fragment));
navController.setGraph(R.navigation.mobile_navigation);
NavigationUI.setupActionBarWithNavController(this, navController, appBarConfiguration);
NavigationUI.setupWithNavController(navView,navController);
navView.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() {
#Override
public boolean onNavigationItemSelected(#NonNull MenuItem menuItem) {
int id = menuItem.getItemId();
if (menuItem.isChecked()) return false;
switch (id)
{
case R.id.navigation_home :
navController.navigate(R.id.action_global_navigation_home);
break;
case R.id.navigation_images :
navController.navigate(R.id.action_global_navigation_images);
break;
case R.id.navigation_videos :
navController.navigate(R.id.action_global_navigation_videos);
break;
case R.id.navigation_songs :
navController.navigate(R.id.action_global_navigation_songs);
break;
case R.id.navigation_notifications :
navController.navigate(R.id.action_global_navigation_notifications);
break;
}
return true;
}
});
}
}
Hope this helps.
I am not entirely sure if this is the answer you are looking for but if you are concerned about managing state the modern way of managing state is by using something called a view model. View models are a component of the MVVM architecture. Their purpose is to hold and expose data to your fragment/activity to display. With the navigation architecture, if you store the data related to each fragment in this view model appropriately your state will be retained in the view model.
That being said I would personally suggest looking into MVVM architecture as well as view models specifically. Otherwise, a brute force for retaining state is by using the savedInstance state throughout fragments and manually saving and recovering important data.
Links:
- Android View Model Component
- Android architecture guide
I think you might need to prevent recreating the fragment upon clicking bottom nav view item twice. bottomNavigationView.setOnNavigationItemReselectedListener { /*Nothing to ignore reselection*/} after NavigationUI.setupWithNavController (bottomNavigationView, navHostFragment.getNavController ());
I'm attempting to restore a list of checkboxes's 'ischecked' state however the boxes are never checked for some reason.
I'm sure I'm overlooking something small.
Any suggestions are appreciated.
Source:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.profile_notification_settings_list_new_message);
pref = getApplicationContext().getSharedPreferences("PREFS", 0);
final int selection = pref.getInt("ChatRepeatPosition", 0);
ButterKnife.bind(this);
getSupportActionBar().setTitle("Repeat Notification In");
list = (ListView) findViewById(R.id.simpleListView);
dataAdapter = new ArrayAdapter<String>(this, R.layout.repeat,
R.id.repeat_tv, text);
list.setAdapter(dataAdapter);
list.setChoiceMode(ListView.CHOICE_MODE_NONE);
if (list != null && selection != 0) {
for (int i = 0; i < list.getCount(); i++) {
CheckBox cb = new CheckBox(this);
cb.setId(i);
if (i == selection) {
cb.setChecked(true);
} else {
CheckBox cbb = new CheckBox(this);
cbb.setChecked(false);
}
}
}
Full Source:
https://pastebin.com/tAMTam3X
Edit:
The first 'answer' shown below is not what I'm attempting on accomplish. I fully understand how to restore the state of a single checkbox. I'm having trouble iterating through my list of checkboxes to restore the state of all of them (I am able to iterate through the list in list.setOnItemClickListener because I can get the view - but I'm not sure how to iterate through the list in oncreate)
checkbox list image
The problem is that you're creating new CheckBox objects and not doing anything with them:
for (int i = 0; i < list.getCount(); i++) {
CheckBox cb = new CheckBox(this);
cb.setId(i);
if (i == selection) {
cb.setChecked(true);
} else {
CheckBox cbb = new CheckBox(this);
cbb.setChecked(false);
}
}
This loops through the size of the list, initializes CheckBoxs, sets the checked state on those CheckBoxs and then ... well, that's it. They are in no way associated with the ListView, so how would its state get updated?
What you want to do is use the setItemChecked method, something like this:
final int selection = pref.getInt("ChatRepeatPosition", 0);
list = (ListView) findViewById(R.id.simpleListView);
list.setItemChecked(selection, true);
And you can delete the for loop that literally does nothing.
If you want to read more than one saved selection, then you would need to read from a database or file since preferences are limited to key / value pairs. But the concept would be the same: read the list of selections, iterated through the list, set the item checked on the list view.
Hope that helps!
try this.. in the on create add your checkbox and initialize it.
cb1 = (CheckBox) findViewById(R.id.yourxmlid);
then add a shared pref to check the state of the check box
sharedpreferences = PreferenceManager.getDefaultSharedPreferences(this);
editor = sharedpreferences.edit();
boolean checkedFlag1 = sharedpreferences.getBoolean("checkboxstate", false);
cb1.setChecked(checkedFlag1);
then finally in your onCheckListener add a shared pref to check the box
cb1.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
#Override
public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
if (cb1.isChecked()) {
editor.putBoolean("checkboxstate", true);
editor.commit();
Log.i("Checkbox ", "Activated");
}else{
editor.putBoolean("checkboxstate", false);
editor.commit();
Log.i("Checkbox: ","Deactivated");
}
}
});
My objective is manage my custom preferences. For this, I have the following code:
MainActivity.java
public class MainActivity extends ActionBarActivity implements View.OnTouchListener, GoogleMap.OnInfoWindowClickListener, LocationListener, GoogleMap.OnMapClickListener{
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == R.id.action_settings) {
launchPreferences(null);
return true;
}
return super.onOptionsItemSelected(item);
}
public void launchPreferences(View view) {
Intent i = new Intent(this, Preferences.class);
startActivity(i);
}
}
Preferences.java
public class Preferences extends PreferenceActivity {
#Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.settings);
updatePreference();
}
settings.xml
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android"
android:key="#+id/preferencias_principal" >
<EditTextPreference
android:key="#+id/perimer"
android:title="Perimeter to list"
android:summary="Maximum distance to list"
android:defaultValue="2"/>
<ListPreference
android:key="#+id/leguage"
android:title="Lenguage"
android:summary="What language would you apply?"
android:entries="#array/lenguage"
android:entryValues="#array/lenguageva"
android:defaultValue="1"/>
</PreferenceScreen>
arrays.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string-array name="lenguage">
<item>English</item>
<item>Basque</item>
<item>Spanish</item>
</string-array>
<string-array name="lenguageva">
<item>1</item>
<item>2</item>
<item>3</item>
</string-array>
<string-array name="number">
<item>1</item>
<item>2</item>
<item>3</item>
<item>4</item>
<item>5</item>
<item>6</item>
<item>7</item>
<item>8</item>
<item>9</item>
<item>10</item>
<item>11</item>
<item>12</item>
<item>13</item>
<item>14</item>
<item>15</item>
</string-array>
</resources>
I want to get the values of this preference, using this code:
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this);
Integer peri = sharedPref.getInt("#+id/perimer", 1);
Integer leng = sharedPref.getInt("#+id/leguage", 2);
But this code return default values, peri=1 and leng=2.
Not only that, sometimes the information added by the preference activity is changed/deleted or in the ListPreference item is not appear the default item selected (as you can be seen in the image).
When I execute the code visible here, with debugger, this is the result:
What can I do?
Thank you!
The main problem is the KEY #+id/perimer and #+id/leguage that you used for your EditTextPreference and ListPreference.
SharedPreferences is unable to recognize it as preference KEY. As you added resource prefix #+id, its treated as int resource id that's why still it works with below lines but doing abnormal behavior and giving you incorrect output.
Integer peri = sharedPref.getInt("#+id/perimer", 1); // WRONG
Integer leng = sharedPref.getInt("#+id/leguage", 2); // WRONG
SOLUTION:
1. Do not use resource prefix #+id with preference KEY. Update your settings.xml as below:
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" >
<EditTextPreference
android:key="perimer"
android:title="Perimeter to list"
android:summary="Maximum distance to list"
android:defaultValue="2"/>
<ListPreference
android:key="leguage"
android:title="Lenguage"
android:summary="What language would you apply?"
android:entries="#array/lenguage"
android:entryValues="#array/lenguageva"
android:defaultValue="1"/>
</PreferenceScreen>
2. Get preference values using SharedPreferences.getString(KEY_NAME, DEFAULT_VALUE).
// Get current values
String peri = mSharedPreferences.getString("perimer", "1");
String leng = mSharedPreferences.getString("leguage", "2");
3. Add OnPreferenceChangeListener to your preferences to get the updated values and update the preference summary if needed.
// Required to get the updated value and update summary when you input distance
mPreferencePerimer.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
#Override
public boolean onPreferenceChange(Preference preference, Object value) {
String peri = value.toString();
//Update premier summary
mPreferencePerimer.setSummary(peri);
return true;
}
});
// Required to get the updated value and update summary when you change language from list
mPreferenceLeguage.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
#Override
public boolean onPreferenceChange(Preference preference, Object value) {
String leng = value.toString();
int lengIndex = Integer.parseInt(leng) - 1;
// Update leguage summary
mPreferenceLeguage.setSummary(mPreferenceLeguage.getEntries()[lengIndex]);
return true;
}
});
Here is the full code:
public class Preferences extends PreferenceActivity {
// Preference Keys
public static final String KEY_PREF_PERIMER = "perimer";
public static final String KEY_PREF_LEGUAGE = "leguage";
// Shared preference
SharedPreferences mSharedPreferences;
EditTextPreference mPreferencePerimer;
ListPreference mPreferenceLeguage;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.settings);
// Shared preference
mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(getActivity());
// Perimer
mPreferencePerimer = (EditTextPreference) getPreferenceScreen().findPreference(KEY_PREF_PERIMER);
// Leguage
mPreferenceLeguage = (ListPreference) getPreferenceScreen().findPreference(KEY_PREF_LEGUAGE);
// Get current values
String peri = mSharedPreferences.getString(KEY_PREF_PERIMER, "1");
String leng = mSharedPreferences.getString(KEY_PREF_LEGUAGE, "2");
int lengIndex = Integer.parseInt(leng) - 1;
// Set preference summary
mPreferencePerimer.setSummary(peri);
mPreferenceLeguage.setSummary(mPreferenceLeguage.getEntries()[lengIndex]);
// Required to get the updated value and update summary when you input distance
mPreferencePerimer.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
#Override
public boolean onPreferenceChange(Preference preference, Object value) {
String peri = value.toString();
//Update premier summary
mPreferencePerimer.setSummary(peri);
return true;
}
});
// Required to get the updated value and update summary when you change language from list
mPreferenceLeguage.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
#Override
public boolean onPreferenceChange(Preference preference, Object value) {
String leng = value.toString();
int lengIndex = Integer.parseInt(leng) - 1;
// Update leguage summary
mPreferenceLeguage.setSummary(mPreferenceLeguage.getEntries()[lengIndex]);
return true;
}
});
}
}
OUTPUT:
DEFAULT STATE
AFTER CHANGE
Hope this will help~
I've done something similar to this and I think this should work. But this code is untested and may throw some error.
public class Preferences extends PreferenceActivity implements android.preference.Preference.OnPreferenceChangeListener {
private ListPreference lang_list;
#Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.settings);
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this);
// get the stored (or default) value (the index of the selected item)
Integer leng = sharedPref.getInt("#+id/leguage", 2);
// get the reference of the ListPreference defined in settings.xml
lang_list = (ListPreference) getPreferenceManager().findPreference("#+id/leguage");
// set the selected value
lang_list.setValueIndex(leng);
// add a listener to get the changes made by the user. Your Preferences class should implement the OnPreferenceChangeListener interface
lang_list.setOnPreferenceChangeListener(this);
}
...
/// When the user selects an item from the list, this function will be fired
public boolean onPreferenceChange(Preference preference, Object newValue) {
String key = preference.getKey();
// ensure that the change has been in the language list
if (key.equals(lang_list.getKey())) {
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this);
// set the value for the key "#+id/leguage"
sharedPref.putInt("#+id/leguage", (int) newValue);
// save the sharedPref object to the file system
sharedPref.apply();
return true;
}
return false;
}
UPDATED 2017/06/09: Your Preferences class must implement the android.preference.Preference.OnPreferenceChangeListener interface in order to work with the line lang_list.setOnPreferenceChangeListener(this);
I have an Activity that looks like this - the length of the list view is dynamic and can change;
Now, I have declared and initialized checkboxes based on the length of the array.
I need to save the states of these boxes when the user clicks on "Save". How do I achieve this? I wrote the following code, but I get no input, i.e. even though the checkboxes are initialized, I don't think the activity knows which checkbox declaration is for which checkbox on the activity. Please help - thanks!
cbs = new CheckBox[length];
for (int i=0;i<length;i++){
cbs[i] = new CheckBox(ThisActivity.this);
}
btnSave.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
DoOnBtnClick(v);
}
});
}
public void DoOnBtnClick (View v) {
for(int i = 0; i < cbs.length; i++){
if(cbs[i].isChecked()){
selectedCheckboxes.add(toInt(cbs[i].getTag()));
Log.e("GET TAG",Integer.toString(toInt(cbs[i].getTag())));
}
}
}
you can use sharedPreferences to store and retrieve checkbox state data
Initialize variables first:
public static final String MyPREFERENCES = "MyPrefs";
SharedPreferences sharedPreferences;
sharedPreferences = getSharedPreferences(MyPREFERENCES, Context.MODE_PRIVATE);
Now in your onCreate() after all checkbox are initialized use setOnCheckedChangeListener
Now you can load data from sharedpreferences using this:
public void Load_checklist() {
SharedPreferences shared = getSharedPreferences(MyPREFERENCES, Context.MODE_PRIVATE);
for(int i = 0; i < cbs.length; i++){
if (shared.getString(Integer.toString(i), "").equals("1")) {
cbs[i].setChecked(true);
}else{
cbs[i].setChecked(false);
}
}
}
finally your onCreate method should look like this:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.layout_name);
int array_length=jArray.length(); //checkbox size
//layout where you want to dynamically add checkboxes
LinearLayout linearLayout = (LinearLayout) findViewById(R.id.lyout);
Load_checklist();
for(int c=0; c<jArray.length();c++){
CheckBox chk=new CheckBox(this);
chk.setId(c++);
chk.setText("Click to add values");
chk.setTextColor(Color.GRAY);
chk.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
#Override
public void onCheckedChanged(CompoundButton buttonView,boolean isChecked) {
String s="x"+buttonView.getId();
Toast.makeText(getApplicationContext(), s, Toast.LENGTH_LONG).show();
switch(buttonView.getId()){
case 1: // do something on 1st checkbox
if (isChecked) {
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putString(Integer.toString(c), "1");
editor.commit();
} else {
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putString(Integer.toString(c), "0");
editor.commit();
}
break;
case 2: //do something on 2nd checkbox
break;
//And SO ON for all checkboes
}
}
});
linearLayout.addView(chk);
}
}
N.B: If SetId(Int) is not working then you can use setTag(int) instead.
If a CheckBox's Tag should represent its index in the list, you should set that tag when you are instantiating them.
for (int i=0;i<length;i++){
cbs[i] = new CheckBox(ThisActivity.this);
cbs[i].setTag(i);
}
I would not recommend this approach if you expect to have a variable amount of checkboxes due to the performance implications. Instead, consider using a RecyclerView with a backing list of model objects representing the state of your checkboxes.
https://developer.android.com/reference/android/support/v7/widget/RecyclerView.html
i have a problem. I want to restore last fragment opened before close my application and i would like to use SharedPreferences so i put into the onCreateView of each fragment a snippet of code that save a string to shared preferences and in the Main Activity i've this
if(savedIstanceState == null) {
pref = new SharedPref(this);
String prefe = pref.getPreString("LastPage");
if(prefe == "0") {
fragment = new Fragment0();
} else if(prefe == "1") {
fragment = new Fragment1();
} else if(prefe == "2") {
fragment = new Fragment2();
} else {
fragment = new Fragment3();
}
getFragmentManager().beginTransaction().add(R.id.container, fragment).commit();
}
But return always Fragment3 also if in the preferences there are for example 0. Why?
Firstly, dont compare Strings using ==, use equals instead. Secondly I assume you are saving correctly your preferences as you did not show how you store the value.
Hope it helps.