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.
Related
I have a back stack of a few fragments and I need to know that I have returned to one from another.
Similar to when you have onActivityResult I was wondering if you could have a check for popBackStack() calls in a fragment?
I call it in another fragment here:
#Override
public void postFinished(){
getFragmentManager().popBackStack();
}
Anything like this in android java?
You Can add Listener for BackStackChange on your Activity page Like,
getSupportFragmentManager().addOnBackStackChangedListener(
new FragmentManager.OnBackStackChangedListener() {
public void onBackStackChanged() {
// Your Code Here
}
});
getSupportFragmentManager().addOnBackStackChangedListener(
new FragmentManager.OnBackStackChangedListener() {
public void onBackStackChanged() {
FragmentManager fm = getSupportFragmentManager();
if (fm != null) {
int backStackCount = fm.getBackStackEntryCount();
if (backStackCount == 0) {
}
}
}
});
You can use addOnBackStackChangedListener with fm.getBackStackEntryCount(); which will give back stack count of fragments.
you can do in onViewCreated() method:
if (savedInstanceState == null && !mAlreadyLoaded) {
mAlreadyLoaded = true;
// Do this code only first time, not after rotation or reuse fragment from backstack
}
Because when android put fragment on backstack, it only destroy its view, but don't kill instance itself, so mAlreadyLoaded will be still true when fragment will be restored from backstack.
I'm working on an app and I have a menu with a NavigationDrawer to navigate between fragments. In one of the fragments I make a call to the backend and then save the results in a list. When I navigate to another fragment and back, the results are gone, but I'd like to save the contents of the list temporarily. I wanted to use onSaveInstanceState(), but that method doesn't seem to get called ever. I also looked if the data is still in the fields when I return to the fragment, but that also wasn't the case. I think I'm doing something wrong with the FragmentManager, but I'm not sure about it.
This is the method used for the transactions for the Fragments:
private void openFragment(Class fragmentClass) {
Fragment fragment;
try {
fragment = (Fragment) fragmentClass.newInstance();
} catch (InstantiationException | IllegalAccessException e) {
e.printStackTrace();
return;
}
contentFrame.removeAllViews();
FragmentManager fragmentManager = getSupportFragmentManager();
fragmentManager.beginTransaction().replace(R.id.contentFrame,fragment).commit();
}
I use a switch case to determine the Fragment's class and send that to this method.
I could probably figure out a hacky-snappy way to fix this, but I'd like to fix this without too much hacky-snappy code.
I hope someone has an idea on how to fix this. Thanks in advance.
EDIT:
Here is my fragment class:
public class LGSFragment extends Fragment {
#BindView(R.id.rvLGS)
RecyclerView rvLGS;
private List<LGS> lgsList;
private LGSAdapter adapter;
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
//I debugged here and all fields were null at this point
View view = inflater.inflate(R.layout.fragment_lgs,container,false);
ButterKnife.bind(this, view);
lgsList = new ArrayList<>();
LinearLayoutManager manager = new LinearLayoutManager(getContext());
rvLGS.setLayoutManager(manager);
adapter = new LGSAdapter(lgsList);
rvLGS.setAdapter(adapter);
getDatabaseLGSs();
return view;
}
/**
* Method to load in the LGSs from the database
*/
private void getDatabaseLGSs() {
String collection = getString(R.string.db_lgs);
FireStoreUtils.getAllDocumentsConverted(collection, LGS.class, new OperationCompletedListener() {
#Override
public void onOperationComplete(Result result, Object... data) {
if (result == Result.SUCCESS) {
lgsList.clear();
List<LGS> newLGSs = (List<LGS>) data[0];
List<String> ids = (List<String>) data[1];
int i = 0;
for (LGS lgs : newLGSs) {
lgs.setId(ids.get(i));
lgsList.add(lgs);
i++;
}
adapter.notifyDataSetChanged();
}
}
});
}
#Override
public void onSaveInstanceState(#NonNull Bundle outState) {
super.onSaveInstanceState(outState);
}
}
onSaveInstanceState is not called because there is no reason to, when you navigate between fragments, the older fragment doesn't get destroyed till the OS need the space they use (low Memory).
First of all create a back stack to keep fragments or just call addtoBackStack at the end of fragmentTransaction and then move the list initiation and data request to onCreate so it only called when the fragment is created:
lgsList = new ArrayList<>();
getDatabaseLGSs();
and after that every time you get back to fragment the view is recreated with available data.
Update:
Instead of keeping an reference on your own, you can add the fragment to the backstack and then retrieve it using corresponding tag. This let's fragmentManager manages the caching by itself. And the second time you access a fragment, it doesn't gets recreated:
#Override
public void onNavigationDrawerItemSelected(#NonNull MenuItem item) {
if (item.isChecked())
return;
item.setChecked(true);
setTitle(item.getTitle());
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction transaction = fragmentManager.beginTransaction();
Fragment currentlyShown = fragmentManager.findFragmentByTag(currentlyShownTag);
Fragment dest;
switch (item.getItemId()){
case R.id.nav_lgs:
dest = fragmentManager.findFragmentByTag(LGSFragment.class.getName());
if (dest == null) {
Log.d("TRANSACTION", "instanciating new fragment");
dest = new LGSFragment();
currentlyShownTag = LGSFragment.class.getName();
transaction.add(R.id.contentFrame, dest, LGSFragment.class.getName());
}
break;
...
}
if(currentlyShown != null)
transaction.hide(currentlyShown);
transaction.show(dest);
transaction.commit();
drawerLayout.closeDrawers();
return true;
}
EDIT:
Although this solution works fine, this solution uses some bad practices, I recommend using the accepted solution instead.
I've solved the problem with the help of Keivan Esbati and denvercoder9 (Thanks for that!)
Since I only have 4 fragments I keep an instance of each of them in the MainActivity, I also have a variable to track the current Fragment. Everytime I open a fragment, I hide the current fragment using the FragmentManager and calling .hide() in the transaction. Then, if the Fragment is a new Fragment I call .add() in the transaction, otherwise I call .show in the transaction.
The code for the onNavigationItemSelected() method (which triggers when a user selects an item in the menu):
public boolean onNavigationItemSelected(#NonNull MenuItem item) {
if (!item.isChecked()) {
item.setChecked(true);
setTitle(item.getTitle());
switch (item.getItemId()) {
case R.id.nav_lgs: {
if (lgsFragment == null) {
lgsFragment = new LGSFragment();
openFragment(lgsFragment, FragmentTag.LGS.toString());
} else {
openFragment(lgsFragment, "");
}
currentFragmentTag = FragmentTag.LGS;
break;
}
case R.id.nav_users: {
if (userFragment == null) {
userFragment = new UserFragment();
openFragment(userFragment, FragmentTag.USERS.toString());
} else {
openFragment(userFragment, "");
}
currentFragmentTag = FragmentTag.USERS;
break;
}
case R.id.nav_profile: {
if (profileFragment == null) {
profileFragment = new ProfileFragment();
openFragment(profileFragment, FragmentTag.PROFILE.toString());
} else {
openFragment(profileFragment, "");
}
currentFragmentTag = FragmentTag.PROFILE;
break;
}
case R.id.nav_my_lgs: {
if (myLGSFragment == null) {
myLGSFragment = new MyLGSFragment();
openFragment(myLGSFragment, FragmentTag.MY_LGS.toString());
} else {
openFragment(myLGSFragment, "");
}
currentFragmentTag = FragmentTag.MY_LGS;
break;
}
default: {
if (lgsFragment == null) {
lgsFragment = new LGSFragment();
openFragment(lgsFragment, FragmentTag.LGS.toString());
} else {
openFragment(lgsFragment, "");
}
currentFragmentTag = FragmentTag.LGS;
break;
}
}
}
drawerLayout.closeDrawers();
return true;
}
The openFragment() method used above:
private void openFragment(Fragment fragment, String tag) {
FragmentManager fragmentManager = getSupportFragmentManager();
if (currentFragmentTag != FragmentTag.NO_FRAGMENT) {
fragmentManager.beginTransaction().hide(fragmentManager.findFragmentByTag(currentFragmentTag.toString())).commit();
}
if (!tag.equals("")) {
fragmentManager.beginTransaction().add(R.id.contentFrame,fragment,tag).commit();
} else {
fragmentManager.beginTransaction().show(fragment).commit();
}
}
Set up in onCreate():
currentFragmentTag = FragmentTag.NO_FRAGMENT;
if (lgsFragment == null) {
lgsFragment = new LGSFragment();
openFragment(lgsFragment, FragmentTag.LGS.toString());
} else {
openFragment(lgsFragment, "");
}
currentFragmentTag = FragmentTag.LGS;
I'm trying to replace fragments in ViewPager, but I'm facing a problem I've been unable to fix for several days. The relevant code and specific problem, as I understand it, are described below:
public class ViewPageAdapter extends FragmentStatePagerAdapter {
int mNumOfTabs;
FragmentManager mFragmentManager;
Fragment0 currentFragment0;
Fragment1 currentFragment1;
Fragment2 currentFragment2;
boolean getItemNeverCalled = true;
public ViewPageAdapter(FragmentManager fm, int numOfTabs){
super(fm);
mFragmentManager = fm;
this.mNumOfTabs = numOfTabs;
}
#Override
public Fragment getItem(int position){
switch (position){
case 0:
if(currentFragment0 == null){
Fragment0 tab0 = new Fragment0();
currentFragment0 = tab0;
return currentFragment0;
}
else {
mFragmentManager.beginTransaction().remove(currentFragment0).commit();
int value = selectedPlant.getMoistureFrag().getStat().getOptimalLevel();
currentFragment0 = Fragment0.newInstance(key0, value);
notifyDataSetChanged(); // calls getItem(0).
return currentFragment0;
}
case 1:
if(currentFragment1 == null){
LightFragment tab1 = new Fragment1();
currentFragment1 = tab1;
return currentFragment1;
}
else {
mFragmentManager.beginTransaction().remove(currentFragment1).commit();
int value = selectedPlant.getLightFrag().getStat().getOptimalLevel();
currentFragment1 = currentFragment1.newInstance(key1, value);
notifyDataSetChanged();
return currentFragment1;
}
case 2:
if(currentFragment2 == null){
Fragment2 tab2 = new Fragment2();
currentFragment2 = tab2;
return currentFragment2;
}
else {
mFragmentManager.beginTransaction().remove(currentFragment2).commit();
int value = selectedPlant.getTempFrag().getStat().getOptimalLevel();
currentFragment2 = Fragment2.newInstance(key2, value);
notifyDataSetChanged();
return currentFragment2;
}
default:
return null;
}
}
#Override
public int getCount(){
return mNumOfTabs;
}
#Override
public int getItemPosition(Object object){
return POSITION_NONE;
}
I've overriden the getItemPosition(Object object) method to always return POSITION_NONE, and called notifyDataSetChanged() when appropriate (I think). What ends up happening is that notifyDataSetChanged() calls getItem(0), which calls `notifyDataSethanged()... and so on. This causes a TransactionTooLargeException and crashes the app.
Just to give some background to the if/else statements in each case: the if is meant to load a blank Moisture/Light/etc Fragment onto the screen. This is intended to happen on start-up. The else statement is executed when a user presses on a item in the navigation drawer, which has some data. This data is then extracted and set as arguments for the fragments that are meant to replace the initial blank fragment.
I genuinely appreciate any help. This problem is driving me crazy.
Why in the world are you recreating fragments, when you can just update the old ones?
Also, when you are calling notifyDataSetChanged during getItem then you are forcing a new call to getItem which will force a new call...so you are actually creating a circular call!
Since you are always keeping the same fragment class in each position, and you are holding on to the fragment, then you should not replace fragment. Just change the fragment you are holding to show the new values. The code you are using is only needed if you want to change different fragment classes for position.
I have a main activity which creates fragments using the following code
private void launchFragment(int pos)
{
Fragment f = null;
String title = null;
if (pos == 1)
{
title = "Friends";
f = new FriendList();
}
else if (pos == 2)
{
title = "Notes";
f = new NoteList();
}
else if (pos == 3)
{
title = "Projects";
f = new ProjectList();
}
else if (pos == 5)
{
title = "About";
f = new AboutUs();
}
else if (pos == 6)
{
startActivity(new Intent(this, Login.class));
finish();
}
if (f != null)
{
while (getSupportFragmentManager().getBackStackEntryCount() > 0)
{
getSupportFragmentManager().popBackStackImmediate();
}
getSupportFragmentManager().beginTransaction()
.replace(R.id.content_frame, f).addToBackStack(title)
.commit();
}
}
Here is the code of a fragment.
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState)
{
View v = inflater.inflate(R.layout.group_chat, null);
loadConversationList();
contactName = this.getArguments().getString("contactusername");
contactId = this.getArguments().getString("contactid");
ListView list = (ListView) v.findViewById(R.id.list);
adp = new ChatAdapter();
list.setAdapter(adp);
list.setTranscriptMode(AbsListView.TRANSCRIPT_MODE_ALWAYS_SCROLL);
list.setStackFromBottom(true);
txt = (EditText) v.findViewById(R.id.txt);
txt.setInputType(InputType.TYPE_CLASS_TEXT
| InputType.TYPE_TEXT_FLAG_MULTI_LINE);
setTouchNClick(v.findViewById(R.id.btnCamera));
setTouchNClick(v.findViewById(R.id.btnSend));
return v;
}
I want to call a method in above fragment class. I was not able to do this as I have not given id of the fragment in the XML file. I am not loading the static fragment using XML. Therefore, I don't have Id.
I have already seen this and this questions on the StackOverFlow, but they are not solving my problem.
Kindly help if anyone knows how to tackle this scenario.
First of all make all your fragments implement an interface. This interface will return a String (for example) which will identify your fragment, and then cast your fragment to it after getting the fragment using findFragmentById() as follows:
Create your interface
public interface IFragmentName
{
public String getFragmentName();
}
Implement your interface (for example in NoteList)
public NoteList extends Fragment implements IFragmentName
{
//Do your stuff...
public String getFragmentName()
{
return "NoteList";
}
}
After this get your current fragment from your activity
IFragmentName myFragment = (IFragmentName) getSupportFragmentManager().findFragmentById(R.id.content_frame);
Finally check your getFragmentName() value and cast to the fragment you want:
if(myFragment.getFragmentName().equals("NoteList")
{
NoteList myNoteListFragment = (NoteList) myFragment;
myNoteListFragment.callMyMethod(); //here you call the method of your current Fragment.
}
I have coded these snippets without any IDE so maybe I have missed a semicolon or something like that :)
Hope it helps
I know I am too late for the party. But this will be useful for others.
If at all you are using ViewPager to render the fragment use this code in your parent Activity.
Check Solution here
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