I have recently just learnt how to use a fragment manager to send data between two fragments as shown in the codes below:
My First fragment that I am sending the data from:
history_fragment fragment = history_fragment.newInstance(InputValue1, InputValue2, InputValue3, InputValue4);
getFragmentManager().beginTransaction().replace(R.id.nav_host_fragment, fragment).commit();
The Fragment that is receiving the data:
public static history_fragment newInstance(String InputValue1, String InputValue2, String InputValue3, String InputValue4) {
history_fragment fragment = new history_fragment();
Bundle args = new Bundle();
args.putInt("Value1", Integer.parseInt(InputValue1));
args.putInt("Value2", Integer.parseInt(InputValue2));
args.putInt("Value3", Integer.parseInt(InputValue3));
args.putInt("Value4", Integer.parseInt(InputValue4));
fragment.setArguments(args);
return fragment;
}
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
TextView value1 = view.findViewById(R.id.Value1);
if ( getArguments() != null) {
Value1 = getArguments().getInt("Value1");
}
value1.setText(String.valueOf(Value1));
}
My code is not fully complete but the main problem am I trying to solve is that whenever I go back to my first fragment, the application crashes. I know that the problem is due to the transaction ".replace()", but how should I stop the transaction before returning back to the first fragment?
Related
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()
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()
I have 3 Fragments A, B and C. When I press a button in fragment A, I have to navigate to fragment B (to initialize it) which should implicitly navigate to fragment C. Meanwhile, it should not be added to backstack, so that when I return back from fragment C, it should directly come back to fragment A. Can someone tell how to do this?
I have tried not adding it to the backstack, but it doesn't work out. It throws NullPointerException while coming back.
final int fragId = manager
.beginTransaction()
.replace(R.id.main_container, fragment, MAIN_FRAGMENT_TAG )
.commitAllowingStateLoss();
if you pass argument correctly to your fragment, when pop from backStack this bundle restore well!
public class HomeProfileFragment extends Fragment {
public static HomeProfileFragment newInstance(String name){
Bundle args = new Bundle();
args.putString("ARG_NAME",name);
HomeProfileFragment fragment = new HomeProfileFragment();
fragment.setArguments(args);
return fragment;
}
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
String name = getArguments().getString("ARG_NAME");
}
}
and just use replace for adding to backStack:
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.frag_container, fragment, fragment.getClass().getSimpleName())
.addToBackStack(fragment.getClass().getSimpleName()).commit();
and implement onBackPressed() :
#Override
public void onBackPressed() {
if (fragmentManager.getBackStackEntryCount() > 0)
fragmentManager.popBackStack();
else
super.onBackPressed();
}
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();
}
So I have an app with a TabLayout. In there, I created 2 tabs (fragments).My first fragment is OngletCours and the 2nd one OngletNotes.
I have a ListView in the first tab/fragment. I would like to transfer the Item I clicked in that ListView to the 2nd Fragment to a TextView.
I have already tried something but I get a NullPointerException at line 23:
03-06 09:38:32.874 23677-23677/com.example.dasilvadd.students E/AndroidRuntime: FATAL EXCEPTION: main
java.lang.NullPointerException
at com.example.dasilvadd.students.OngletNotes.onCreateView(OngletNotes.java:23)
here's the code from my 1st tab OngletCours (Only the setOnItemClickListener):
final ListView l1 = (ListView) rootView.findViewById(R.id.ListCours);
l1.setOnItemClickListener(new AdapterView.OnItemClickListener()
{
#Override
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
OngletNotes fragment = ((Onglets)getActivity()).getOngletNotes ();
if(fragment != null) {
l1.getItemAtPosition(i);
//Put the value
OngletNotes onotes = new OngletNotes();
Bundle args = new Bundle();
args.putString("Item","l1.getItemAtPosition(i)");
onotes.setArguments(args);
getFragmentManager().beginTransaction().add(R.id.container, onotes).commit();
}
((Onglets)getActivity()).goToFragment(1);
}
});
return rootView;
}
Here's the code from my 2nd tab OngletNotes:
public class OngletNotes extends Fragment {
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.ongletnotes, container, false);
//where i want to insert the selectedItem
TextView Cours = (TextView)rootView.findViewById(R.id.TVCours);
//Retrieve the value
//THE ERROR IS HERE !
String cours = getArguments().getString("Item");
Cours.setText(cours);
return rootView;
}
public static OngletNotes newInstance() {
OngletNotes fragment = new OngletNotes();
return fragment;
}
So, I don't know how to transfer the selected Item into the 2nd tab.
Do I have to create methodes in my TabbedActivity called Onglets? Please tell me if you need the code for that class as well.
Thank you in advance.
I think you got a bit confused in your logic. You are checking for null and then not doing anything if the fragment is null.
You can simplify it like this:
First check if fragment is null , if so create a new instance, then
put the arguments outside the if/else so you make sure you get them every time.
Also put your string item in a variable.
#Override
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
OngletNotes fragment = ((Onglets)getActivity()).getOngletNotes ();
if(fragment == null) {
fragment = OngletNotes.getInstance();
}
String item = adapterView.getItemAtPosition(i).toString();
//Put the value
Bundle args = new Bundle();
args.putString("Item",item);
fragment.setArguments(args);
getFragmentManager().beginTransaction().add(R.id.container, fragment).commit();
}