SavedInstanceStateNull between Fragments is null - java

I have a fragment which shows a list of buttons, each have their own id. When the user clicks the button, I am trying to replace the current fragment with another. Right after I replace the fragment, I a am adding arguments to the bundle. From what I can tell from debug mode, that part is working 100%. However, when we get to the new fragment, the "savedInstanceState" state will always be null.
I have read several posts on this however, I am confused because I am attempting to set the bundle and replace the fragment the same way I did when going from the activity to the first fragment and it is still not working.
MyActivity.java (this is working and all values are being set):
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
MyFirstFragment fragment = new MyFirstFragment();
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
fragmentTransaction.replace(R.id.frame,fragment);
fragmentTransaction.commit();
Intent i = getIntent();
value = i.getExtras().getString("key");
Bundle bundle = new Bundle();
bundle.putString("key",value);
fragment.setArguments(bundle);
}
MyFirstFragment.java (also working):
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_first, container, false);
final String value = getArguments().getString("key");
myButton.setOnClickListener(new View.OnClickListener(){
#Override
public void onClick(View v){
String gid = Integer.toString(v.getId());
MySecondFragment secondfragment = new MySecondFragment();
Bundle secondFragmentBundle = new Bundle();
secondFragmentBundle.putString("key",value);
secondFragmentBundle.putString("id",id);
secondfragment.setArguments(secondFragmentBundle);
FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction();
fragmentTransaction.replace(R.id.frame,secondfragment);
fragmentTransaction.commit();
}
});
MySecondFragment (getting null for savedInstanceState and all bundle agruments!):
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
view = inflater.inflate(R.layout.fragment_second, container, false);
value = getArguments().getString("key");
id = getArguments().getString("id");
}

You cannot set arguments on a Fragment after it is attached to an Activity.
Your issue is caused by confusion about Fragment creation. What you provided in your question is not the correct way to send arguments to another Fragment type.
The de-facto standard way of passing arguments to a Fragment when it is created it to use a static factory method, generally called newInstance(). You should set the arguments this way, and should do so before committing the FragmentTransaction - however, you technically could do it right after committing the transaction with no ill effects. This is because when a Fragment is committed, it isn't immediately attached/created, rather, it is queued on the main thread.
Here is how you would do it:
Write a method in your Fragment class called new instance. Be sure to leave a public default (no arguments) constructor for the system to handle things in the background.
public MyFirstFragment extends Fragment {
public MyFirstFragment(){
//constructor - don't use this to create a new fragment directly
}
public static MyFirstFragment newInstance(String argument1, String argument2) {
// This is where you set the Fragment arguments:
MyFirstFragment instance = new MyFirstFragment();
Bundle arguments = new Bundle();
// add whatever arguments you need to the bundle
arguments.putString("ARGUMENT_1_KEY", argument1);
arguments.putString("ARGUMENT_2_KEY", argument2);
//Set the arguments on the new fragment instance
instance.setArguments(arguments);
return instance;
}
// recover the values in onCreate:
#Override
public void onCreate(Bundle savedInstanceState) {
// an easy way to check if this is a new instance is to null-check the saved state Bundle
if (savedInstanceState == null) {
String argument1 = getArguments().getString("ARGUMENT_1_KEY");
String argument2 = getArguments().getString("ARGUMENT_2_KEY");
} else {
// restore whatever you need from savedInstanceState bundle
}
}
Key point - the arguments are passed into the newInstance() method (these can be whatever you want, as long as the type is allowed). The newInstance() method enforces argument types and also prevents setting them after the fragment is already attached to an activity.
After this, in your Activity.onCreate() method:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
String argument1 = "some information";
String argument2 = "other information";
MyFirstFragment fragment = MyFirstFragment.newInstance(argument1, argument2);
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
fragmentTransaction.replace(R.id.frame,fragment);
fragmentTransaction.commit();
}

Related

How do I update my Fragment after data modification in my activity (SQLite)?

I have a ListView in a Fragment that is populated when the app starts.
I put a ParcelableArrayList in a Bundle in my newInstance method, and I get it back in my OnCreateView after passing the ArrayList in the newInstance method in my Activity (which is the data read from the SQLite database).
This part works, as I display my data in my Fragment correctly.
I implemented a button that removes all data from the table, and I would now like to update my view after I cleaned the table.
The remove all button is handled in my main activity where I call my database handler to empty the table.
What is the best way to do that ? Here are the parts of the code that seem relevant to me :
My Fragment class :
public class MainFragment extends Fragment {
public static final String RECETTES_KEY = "recettes_key";
private List<Recette> mRecettes;
private ListView mListView;
public MainFragment() {
// Required empty public constructor
}
public static MainFragment newInstance(List<Recette> r) {
MainFragment fragment = new MainFragment();
Bundle bundle = new Bundle();
bundle.putParcelableArrayList(RECETTES_KEY, (ArrayList<? extends Parcelable>) r);
fragment.setArguments(bundle);
return fragment;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
mRecettes = getArguments().getParcelableArrayList(RECETTES_KEY);
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_main, container, false);
}
#Override
public void onViewCreated(#NonNull View view, #Nullable Bundle savedInstanceState) {
configureListView();
}
// Configure ListView
private void configureListView(){
this.mListView = getView().findViewById(R.id.activity_main_list_view);
RecetteAdapter adapter = new RecetteAdapter(getContext(), mRecettes);
mListView.setAdapter(adapter);
}
}
Relevant parts from my Main acivity :
This is in my OnCreate method :
mDatabaseHandler = new DatabaseHandler(this);
mRecettes = mDatabaseHandler.readRecettes();
mDatabaseHandler.close();
This is in the method I use to show a fragment :
if (this.mMainFragment == null) this.mMainFragment = MainFragment.newInstance(mRecettes);
this.startTransactionFragment(this.mMainFragment);
Let me know if I should add more of my code, this is my first time posting :)
Lucile
In your R.layout.fragment_main, you can add an id to the root view, say with android:id#+id/fragment_root
And whenever you want to change the fragment view:
In activity:
MainFragment fragment = (MainFragment) getSupportFragmentManager().getFragmentById(R.id.fragment_root);
fragment.updateList(mRecettes);
And then create the new method updateList() in your MainFragment
public void updateList(List<Recette> recettes) {
mRecettes.clear();
mRecettes.addAll(recettes);
configureListView();
}
Also you can tag your fragment when you add it to your transaction instead of using its id, and then use getSupportFragmentManager().getFragmentByTag()

Unable to send Strings from one Fragment to another via Bundles

I am trying to send two strings via a bundle to the next fragment but the app crashes even before I get to the first fragment.
I get this error in the second fragment:
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.example.android.guessit, PID: 22360
java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.String android.os.Bundle.getString(java.lang.String)' on a null object reference
at com.example.android.guessit.GameFlow.FragmentAnswer1.onCreateView(FragmentAnswer1.java:125)
at androidx.fragment.app.Fragment.performCreateView(Fragment.java:2600)
at androidx.fragment.app.FragmentManagerImpl.moveToState(FragmentManagerImpl.java:881)
at androidx.fragment.app.FragmentManagerImpl.moveFragmentToExpectedState(FragmentManagerImpl.java:1238)
at androidx.fragment.app.FragmentManagerImpl.moveToState(FragmentManagerImpl.java:1303)
at androidx.fragment.app.BackStackRecord.executeOps(BackStackRecord.java:439)
at androidx.fragment.app.FragmentManagerImpl.executeOps(FragmentManagerImpl.java:2079)
at androidx.fragment.app.FragmentManagerImpl.executeOpsTogether(FragmentManagerImpl.java:1869)
at androidx.fragment.app.FragmentManagerImpl.removeRedundantOperationsAndExecute(FragmentManagerImpl.java:1824)
at androidx.fragment.app.FragmentManagerImpl.execSingleAction(FragmentManagerImpl.java:1696)
at androidx.fragment.app.BackStackRecord.commitNowAllowingStateLoss(BackStackRecord.java:299)
at androidx.fragment.app.FragmentStatePagerAdapter.finishUpdate(FragmentStatePagerAdapter.java:259)
at androidx.viewpager.widget.ViewPager.populate(ViewPager.java:1244)
at androidx.viewpager.widget.ViewPager.setCurrentItemInternal(ViewPager.java:669)
at androidx.viewpager.widget.ViewPager.setCurrentItemInternal(ViewPager.java:631)
at androidx.viewpager.widget.ViewPager.setCurrentItem(ViewPager.java:612)
at com.example.android.guessit.GameFlow.GameActivity.setViewPager(GameActivity.java:152)
at com.example.android.guessit.GameFlow.FragmentOneCategory$6.onClick(FragmentOneCategory.java:190)
at android.view.View.performClick(View.java:7352)
at android.view.View.performClickInternal(View.java:7318)
at android.view.View.access$3200(View.java:846)
at android.view.View$PerformClick.run(View.java:27800)
at android.os.Handler.handleCallback(Handler.java:873)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:214)
at android.app.ActivityThread.main(ActivityThread.java:7050)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:494)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:965)
I know that this means that the string value that I am trying to receive in the second fragment means that there is no value for that string but I don't know where the problem here is because the strings I am trying to send are not null.
This is the code I am using for the bundle in the first fragment (using this in onCreatView):
FragmentAnswer1 frag1 = new FragmentAnswer1();
Bundle args = new Bundle();
args.putString("question_1", question);
args.putString("answer_1", answer);
frag1.setArguments(args);
getFragmentManager().beginTransaction().add(R.id.container, frag1).commit(); // there is a message that says that "beginTransaction" may produce a nullPointer
Here is the code to receive the strings in the second fragment (currently also in onCreateView)
question = getArguments().getString("question_1"); // error is in this line
answer = getArguments().getString("answer_1");
question1.setText(question);
rightAnswer.setText(answer);
I already tried to move the above code into the onStartmethod but I still get the same error. I also tried using this solution but its not working for me. I did not find any other solution or method on how to resolve the problem.
Please let me know if you need more informations. Any help is much appreciated!
I have now found the correct way to send data from one Fragment to another while having a ViewPager. We have to use a ViewModel.
First we have to create a ViewModel, in this case our goal is to send a String from Fragment 1 to Fragment 2. Here is the code for the ViewModel with Getter and Setter method for the String:
public class ViewModelString extends ViewModel {
private MutableLiveData<String> mString = new MutableLiveData<>();
public MutableLiveData<String> getmString() {
return mString;
}
public void setmString(MutableLiveData<String> mString) {
this.mString = mString;
}
}
Then in Fragment 1 from where we want to sent the string we need to first initialize the ViewModel in onCreate.
public class Fragment1 extends Fragment {
private ViewModelString viewModel;
public Fragment1() {
// Required empty public constructor
}
public static Fragment1 newInstance() {
return new Fragment1();
}
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// initialize ViewModel here
viewModel = ViewModelProviders.of(requireActivity()).get(ViewModelString.class);
}
#Nullable
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
String hello = "hello";
viewModel.setmString(hello);
}
}
Then in the second Fragment we also have to first initialize the viewModel and then create an observer. Here we then take the String that is stored in the ViewModel and set a TextView to its value:
public class Fragment2 extends Fragment {
private ViewModelString viewModel;
public Fragment2() {
// Required empty public constructor
}
public static Fragment2 newInstance() {
return new Fragment2();
}
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// initialize ViewModel here
viewModel = ViewModelProviders.of(requireActivity()).get(ViewModelString.class);
}
#Nullable
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
TextView textView = findViewById(R.id.yourTextViewId);
// Gets the string
viewModel.getmString().observe(requireActivity(), new Observer<String>() {
#Override
public void onChanged(#Nullable String s) {
textView.setText(s);
}
});
}
...
}
I hope this helps anyone! Please feel free to edit my post.
Use Bundle to send String:
//Put the value
Bundle args = new Bundle();
args.putString("YourKey", "YourValue");
YourFragmentName ldf = new YourFragmentName ();
ldf.setArguments(args);
//Inflate the fragment
getActivity().getSupportFragmentManager().beginTransaction().add(R.id.container, ldf).commit();
In onCreateView of the new Fragment:
//Retrieve the value
Bundle bundle = this.getArguments();
if (bundle != null) {
String value = bundle.getString("YourKey");
}
Use a Bundle. Here's an example:
Fragment fragment = new Fragment();
Bundle bundle = new Bundle();
bundle.putInt("Key", "value");
fragment.setArguments(bundle);
fragmentManager.beginTransaction().replace(R.id.container, frag1).commit()
Bundle has put methods for lots of data types. See this
Then in your Fragment, retrieve the data in onCreate() method like:
Bundle bundle = this.getArguments();
if (bundle != null) {
int myInt = bundle.getString(Key);
}
try this.
var fragment: MainFragment =MainFragment()
val args = Bundle()
args.putString("question_1", "My question")
args.putString("answer_1", "My answer")
fragment.setArguments(args)
fragmentManager.beginTransaction().add(R.id.fragment_content, fragment).commit()

NPE in Fragment when setting textView

I Have host MainActivity and 2 Fragments: MainFragment and StringsFragment.
I want to set text in TextView (which is in MainFragment) from StringsFragment.
So in StringsFragment I have:
public interface OnFragmentInteractionListener {
void messengeFromBroadcastFragment(String value);
}
public void onClick(View view) {
String myValue = "helloWorld";
mListener.messengeFromBroadcastFragment(myValue);
}
In MainFragment:
TextView tv;
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.fragment_main, container, false);
tv = view.findViewById(R.id.tv);
return view;
}
public void setTextView(String value){
tv.setText(value);
}
and MainActivity:
public void messengeFromBroadcastFragment(String value) {
mainFragment = new MainFragment();
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.fragmentContainer, mainFragment);
transaction.addToBackStack(null);
transaction.commit();
mainFragment.setTextView(value);
}
And when I call onClick from StringsFragment, I got nullpointer:
setText(java.lang.CharSequence)' on a null object reference
from here
public void setTextView(String value){
tv.setText(value);
}
Looks like when you call
mainFragment.setTextView(value);
Your tv TextView is not exists yet.
For fixing it you have to use Bundle for passing String from Activity to fragment.
Also there are another variants like:
Storing values in Singleton
Storing values in Shared Preferences
Creating queue of values you should display with Observer pattern
Using ReplayRelay from RxRelay library as RxBus . Actually
almost the same as 3rd option
So there will be a bit less boilerplate than using Bundle

Communication between one fragment to another [duplicate]

This question already has answers here:
How to pass data between fragments
(13 answers)
Closed 6 years ago.
I am new to fragments. I added two edit texts in first fragment and I want to send that edittext data to second fragment.
I am using a bundle, but its printing null in second fragment.
Can anyone tell me how to send data to other fragment?
First fragment
nextt.setOnClickListener(new Button.OnClickListener() {
#Override
public void onClick(View _view) {
int viewId = _view.getId();
FragmentTransaction ft;
switch (viewId) {
case R.id.Button1:
FragmentManager fm = getFragmentManager();
ft = fm.beginTransaction();
SecondFrag secondFrag = new SecondFrag();
Bundle bundle = new Bundle();
bundle.putInt("deviceInst",viewId);
secondFrag.setArguments(bundle);
ft.replace(R.id.total_frame_content, secondFrag);
ft.addToBackStack(null);
ft.commit();
break;
}
}
});
In second fragment
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.second_new, container, false);
String value = getArguments().getString("deviceInst");
System.out.println("TTTT"+ value);
In your first fragment you pass an int as an argument.
bundle.putInt("deviceInst",viewId);
And then, in your second fragment you try to get that argument by using
getArguments().getString("deviceInst")
which will fail, so to get the argument you pass you need to use getArguments().getInt("deviceInst")
For encapsulating the needed data, a good tip is to have a static newInstance() method in your fragments that requires data.
Here's a post about it.
https://plus.google.com/+AndroidDevelopers/posts/bCD7Zvd945d
You need to change like
int value = getArguments().getInt("deviceInst");
instead of
String value = getArguments().getString("deviceInst");
You will need to make the following changes in your code.
bundle.putString("deviceInst",editText.getText().toString());
Then in your second fragment you can get that argument by using
getArguments().getString("deviceInst")
Here editText is the instance of the edit text in the first fragment.
As you have passed int in the bundle, you need to use getInt() in your receiver fragment.
For example:
SecondFrag secondFrag = new SecondFrag();
Bundle bundle = new Bundle();
bundle.putInt("deviceInst",viewId);
secondFrag.setArguments(bundle);
getFragmentManager().beginTransaction().add(R.id.total_frame_content, secondFrag).commit();
In receiver fragment
String value = getArguments().getInt("deviceInst");

Removing a fragment not working

I want to remove a fragment and show a toast when I click a textView. My code shows the toast, but doesn't remove the fragment.
My method:
public void hide(View view) {
My_frag myFrag= new My_frag();
FragmentTransaction transaction = getSupportFragmentManager()
.beginTransaction();
transaction.remove(myFrag);
transaction.commit();
Toast.makeText(getApplicationContext(), "Hello", Toast.LENGTH_LONG)
.show();
}
My_frag class:
public class My_frag extends android.support.v4.app.Fragment {
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.my_frag_layout, container, false);
}
}
At some point you must create and add the Fragment correct? You are re-creating the fragment in your hide(...) method so you are trying to remove something that has never been added. Sure you may have added an instance, but not the instance you are trying to remove.
Instead, create a global variable Fragment fragToRemove in your Activity. When you create the fragment (that is where ever you do transaction.add(fragToRemove = new My_Frag);) you will hold an instance. then change your transaction.remove(myFrag) to transaction.remove(fragToRemove) and it should work just fine.
Take the instance of Fragment Transaction other than that was taken while adding fragment to activity and call remove method on that and pass the same instance of Fragment which was used at the time.
Example:
public class MainActivity extends AppCompatActivity {
FragmentTransaction fragmentTransaction;
BlankFragment blankFragment;
Button b;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
b = (Button) findViewById(R.id.activity_button);
fragmentTransaction = getSupportFragmentManager().beginTransaction();
blankFragment=new BlankFragment(); //Fragment instance
fragmentTransaction.add(R.id.main_layout,blankFragment).commit();
b.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.remove(blankFragment).commit(); //created different
}
});
}
In the same way you can do in fragments also and can also remove fragment X on the click of component of fragment X.

Categories