Fragments make retrofit request again when switches back to the fragment - android

I have bottom navigation bar implemented in MainActivity class having two fragments.I am making retrofit request in both fragments problem is when I am switching fragments and coming back to the previous one then it makes request again and previous data has lost.I don't want this I want data to be persist in fragments once request is made.
Below is my code:
MainActivity.class
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
toolbar.setTitle("Home");
HomeFragment fragment = new HomeFragment();
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
fragmentTransaction.replace(R.id.container, fragment);
fragmentTransaction.commit();
bottomBar = findViewById(R.id.bottomBar);
bottomBar.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() {
#Override
public boolean onNavigationItemSelected(#NonNull MenuItem item) {
switch(item.getItemId()){
case R.id.navigation_home:
HomeFragment fragment = new HomeFragment();
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
fragmentTransaction.replace(R.id.container, fragment);
fragmentTransaction.commit();
toolbar.setTitle("Home");
return true;
case R.id.navigation_video:
VideoFragment fragment1 = new VideoFragment();
FragmentTransaction fragmentTransaction1 = getSupportFragmentManager().beginTransaction();
fragmentTransaction1.replace(R.id.container, fragment1);
fragmentTransaction1.commit();
toolbar.setTitle("Videos");
return true;
}
return false;
}
});
}
HomeFragment.class
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.fragment_home, container, false);
recycle = view.findViewById(R.id.recycle);
LinearLayoutManager mLayoutManager = new LinearLayoutManager(getActivity());
mLayoutManager.setReverseLayout(true);
mLayoutManager.setStackFromEnd(true);
recycle.setLayoutManager(mLayoutManager);
recycle.setHasFixedSize(true);
loadFacts();
return view;
}
VideoFragment.class
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.fragment_video, container, false);
recycle = view.findViewById(R.id.recycle);
LinearLayoutManager mLayoutManager = new LinearLayoutManager(getActivity());
mLayoutManager.setReverseLayout(true);
mLayoutManager.setStackFromEnd(true);
recycle.setLayoutManager(mLayoutManager);
recycle.setHasFixedSize(true);
loadVideos();
return view;
}
Someone please let me know how can I get desired result any help would be appreciated.
THANKS

I think problem here is you use fragmentTransaction.replace. So when you switch tab. It destroy current fragment and add a new fragment, your fragment create again. To solve this I recommend to use viewpager.

Once you get your data you can cache them to a Repository. Each time you need your data check if the data already exist, if not download them again.

If you see the fragment lifecycle, while recreating it starts from onCreateView and onAttach & onCreate are called only when fragment is creating for first time. So, what you can do is
Make the API call and initialization of all variables, data of which you want to persist, in onCreate method only.
Use ViewModel instead as it retains it's state even if fragment is recreated.
If both the fragments are shown as two tabs, I hope you've implemented them using ViewPager only. It retains the state by default if you swipe in between two fragments.

Make A common class and make a static variable that stores whether the page is visited first time or later and if it is visited later then dont load data else load data.
In Common class make a boolean variable
public static boolean first=false;
Then In Your Activity do this
if(Common.first==false)
{
Common.first=true;
loadVideos();
}
else
{
//dont load list
}

Related

Android-Save state of Fragment with RecyclerView [duplicate]

I've written up a dummy activity that switches between two fragments. When you go from FragmentA to FragmentB, FragmentA gets added to the back stack. However, when I return to FragmentA (by pressing back), a totally new FragmentA is created and the state it was in is lost. I get the feeling I'm after the same thing as this question, but I've included a complete code sample to help root out the issue:
public class FooActivity extends Activity {
#Override public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final FragmentTransaction transaction = getFragmentManager().beginTransaction();
transaction.replace(android.R.id.content, new FragmentA());
transaction.commit();
}
public void nextFragment() {
final FragmentTransaction transaction = getFragmentManager().beginTransaction();
transaction.replace(android.R.id.content, new FragmentB());
transaction.addToBackStack(null);
transaction.commit();
}
public static class FragmentA extends Fragment {
#Override public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
final View main = inflater.inflate(R.layout.main, container, false);
main.findViewById(R.id.next_fragment_button).setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
((FooActivity) getActivity()).nextFragment();
}
});
return main;
}
#Override public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
// Save some state!
}
}
public static class FragmentB extends Fragment {
#Override public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.b, container, false);
}
}
}
With some log messages added:
07-05 14:28:59.722 D/OMG ( 1260): FooActivity.onCreate
07-05 14:28:59.742 D/OMG ( 1260): FragmentA.onCreateView
07-05 14:28:59.742 D/OMG ( 1260): FooActivity.onResume
<Tap Button on FragmentA>
07-05 14:29:12.842 D/OMG ( 1260): FooActivity.nextFragment
07-05 14:29:12.852 D/OMG ( 1260): FragmentB.onCreateView
<Tap 'Back'>
07-05 14:29:16.792 D/OMG ( 1260): FragmentA.onCreateView
It's never calling FragmentA.onSaveInstanceState and it creates a new FragmentA when you hit back. However, if I'm on FragmentA and I lock the screen, FragmentA.onSaveInstanceState does get called. So weird...am I wrong in expecting a fragment added to the back stack to not need re-creation? Here's what the docs say:
Whereas, if you do call addToBackStack() when removing a fragment,
then the fragment is stopped and will be resumed if the user navigates
back.
If you return to a fragment from the back stack it does not re-create the fragment but re-uses the same instance and starts with onCreateView() in the fragment lifecycle, see Fragment lifecycle.
So if you want to store state you should use instance variables and not rely on onSaveInstanceState().
Comparing to Apple's UINavigationController and UIViewController, Google does not do well in Android software architecture. And Android's document about Fragment does not help much.
When you enter FragmentB from FragmentA, the existing FragmentA instance is not destroyed. When you press Back in FragmentB and return to FragmentA, we don't create a new FragmentA instance. The existing FragmentA instance's onCreateView() will be called.
The key thing is we should not inflate view again in FragmentA's onCreateView(), because we are using the existing FragmentA's instance. We need to save and reuse the rootView.
The following code works well. It does not only keep fragment state, but also reduces the RAM and CPU load (because we only inflate layout if necessary). I can't believe Google's sample code and document never mention it but always inflate layout.
Version 1(Don't use version 1. Use version 2)
public class FragmentA extends Fragment {
View _rootView;
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
if (_rootView == null) {
// Inflate the layout for this fragment
_rootView = inflater.inflate(R.layout.fragment_a, container, false);
// Find and setup subviews
_listView = (ListView)_rootView.findViewById(R.id.listView);
...
} else {
// Do not inflate the layout again.
// The returned View of onCreateView will be added into the fragment.
// However it is not allowed to be added twice even if the parent is same.
// So we must remove _rootView from the existing parent view group
// (it will be added back).
((ViewGroup)_rootView.getParent()).removeView(_rootView);
}
return _rootView;
}
}
------Update on May 3 2005:-------
As the comments mentioned, sometimes _rootView.getParent() is null in onCreateView, which causes the crash. Version 2 removes _rootView in onDestroyView(), as dell116 suggested. Tested on Android 4.0.3, 4.4.4, 5.1.0.
Version 2
public class FragmentA extends Fragment {
View _rootView;
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
if (_rootView == null) {
// Inflate the layout for this fragment
_rootView = inflater.inflate(R.layout.fragment_a, container, false);
// Find and setup subviews
_listView = (ListView)_rootView.findViewById(R.id.listView);
...
} else {
// Do not inflate the layout again.
// The returned View of onCreateView will be added into the fragment.
// However it is not allowed to be added twice even if the parent is same.
// So we must remove _rootView from the existing parent view group
// in onDestroyView() (it will be added back).
}
return _rootView;
}
#Override
public void onDestroyView() {
if (_rootView.getParent() != null) {
((ViewGroup)_rootView.getParent()).removeView(_rootView);
}
super.onDestroyView();
}
}
WARNING!!!
This is a HACK! Though I am using it in my app, you need to test and read comments carefully.
I guess there is an alternative way to achieve what you are looking for.
I don't say its a complete solution but it served the purpose in my case.
What I did is instead of replacing the fragment I just added target fragment.
So basically you will be going to use add() method instead replace().
What else I did.
I hide my current fragment and also add it to backstack.
Hence it overlaps new fragment over the current fragment without destroying its view.(check that its onDestroyView() method is not being called. Plus adding it to backstate gives me the advantage of resuming the fragment.
Here is the code :
Fragment fragment=new DestinationFragment();
FragmentManager fragmentManager = getFragmentManager();
android.app.FragmentTransaction ft=fragmentManager.beginTransaction();
ft.add(R.id.content_frame, fragment);
ft.hide(SourceFragment.this);
ft.addToBackStack(SourceFragment.class.getName());
ft.commit();
AFAIK System only calls onCreateView() if the view is destroyed or not created.
But here we have saved the view by not removing it from memory. So it will not create a new view.
And when you get back from Destination Fragment it will pop the last FragmentTransaction removing top fragment which will make the topmost(SourceFragment's) view to appear over the screen.
COMMENT: As I said it is not a complete solution as it doesn't remove the view of Source fragment and hence occupying more memory than usual. But still, serve the purpose. Also, we are using a totally different mechanism of hiding view instead of replacing it which is non traditional.
So it's not really for how you maintain the state, but for how you maintain the view.
I would suggest a very simple solution.
Take the View reference variable and set view in OnCreateView. Check if view already exists in this variable, then return same view.
private View fragmentView;
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
super.onCreateView(inflater, container, savedInstanceState);
if (fragmentView != null) {
return fragmentView;
}
View view = inflater.inflate(R.layout.yourfragment, container, false);
fragmentView = view;
return view;
}
I came across this problem in a Fragment containing a map, which has too many setup details to save/reload.
My solution was to basically keep this Fragment active the whole time (similar to what #kaushal mentioned).
Say you have current Fragment A and wants to display Fragment B.
Summarizing the consequences:
replace() - remove Fragment A and replace it with Fragment B. Fragment A will be recreated once brought to the front again
add() - (create and) add a Fragment B and it overlap Fragment A, which is still active in the background
remove() - can be used to remove Fragment B and return to A. Fragment B will be recreated when called later on
Hence, if you want to keep both Fragments "saved", just toggle them using hide()/show().
Pros: easy and simple method to keep multiple Fragments running
Cons: you use a lot more memory to keep all of them running. May run into problems, e.g. displaying many large bitmaps
onSaveInstanceState() is only called if there is configuration change.
Since changing from one fragment to another there is no configuration change so no call to onSaveInstanceState() is there. What state is not being save? Can you specify?
If you enter some text in EditText it will be saved automatically. Any UI item without any ID is the item whose view state shall not be saved.
first: just use add method instead of replace method of FragmentTransaction class then you have to add secondFragment to stack by addToBackStack method
second :on back click you have to call popBackStackImmediate()
Fragment sourceFragment = new SourceFragment ();
final Fragment secondFragment = new SecondFragment();
final FragmentTransaction ft = getChildFragmentManager().beginTransaction();
ft.add(R.id.child_fragment_container, secondFragment );
ft.hide(sourceFragment );
ft.addToBackStack(NewsShow.class.getName());
ft.commit();
((SecondFragment)secondFragment).backFragmentInstanceClick = new SecondFragment.backFragmentNewsResult()
{
#Override
public void backFragmentNewsResult()
{
getChildFragmentManager().popBackStackImmediate();
}
};
Kotlin and ViewBinding Solution
I am using replace() and backstack() method for FragmentTransaction. The problem is that the backstack() method calls the onCreateView of the Previous Fragment which causes in re-built of Fragment UI. Here is a solution for that:
private lateinit var binding: FragmentAdRelevantDetailsBinding
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,savedInstanceState: Bundle?
): View {
if (!this::binding.isInitialized)
binding = FragmentAdRelevantDetailsBinding.inflate(layoutInflater, container, false)
return binding.root
}
Here, since onSaveInstanceState in fragment does not call when you add fragment into backstack. The fragment lifecycle in backstack when restored start onCreateView and end onDestroyView while onSaveInstanceState is called between onDestroyView and onDestroy. My solution is create instance variable and init in onCreate. Sample code:
private boolean isDataLoading = true;
private ArrayList<String> listData;
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
isDataLoading = false;
// init list at once when create fragment
listData = new ArrayList();
}
And check it in onActivityCreated:
public void onViewCreated(View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
if(isDataLoading){
fetchData();
}else{
//get saved instance variable listData()
}
}
private void fetchData(){
// do fetch data into listData
}
getSupportFragmentManager().addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener()
{
#Override
public void onBackStackChanged()
{
if (getSupportFragmentManager().getBackStackEntryCount() == 0)
{
//setToolbarTitle("Main Activity");
}
else
{
Log.e("fragment_replace11111", "replace");
}
}
});
YourActivity.java
#Override
public void onBackPressed()
{
Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.Fragment_content);
if (fragment instanceof YourFragmentName)
{
fragmentReplace(new HomeFragment(),"Home Fragment");
txt_toolbar_title.setText("Your Fragment");
}
else{
super.onBackPressed();
}
}
public void fragmentReplace(Fragment fragment, String fragment_name)
{
try
{
fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.replace(R.id.Fragment_content, fragment, fragment_name);
fragmentTransaction.setCustomAnimations(R.anim.enter_from_right, R.anim.exit_to_left, R.anim.enter_from_left, R.anim.exit_to_right);
fragmentTransaction.addToBackStack(fragment_name);
fragmentTransaction.commitAllowingStateLoss();
}
catch (Exception e)
{
e.printStackTrace();
}
}
My problem was similar but I overcame me without keeping the fragment alive. Suppose you have an activity that has 2 fragments - F1 and F2. F1 is started initially and lets say in contains some user info and then upon some condition F2 pops on asking user to fill in additional attribute - their phone number. Next, you want that phone number to pop back to F1 and complete signup but you realize all previous user info is lost and you don't have their previous data. The fragment is recreated from scratch and even if you saved this information in onSaveInstanceState the bundle comes back null in onActivityCreated.
Solution:
Save required information as an instance variable in calling activity. Then pass that instance variable into your fragment.
#Override
public void onActivityCreated(#Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
Bundle args = getArguments();
// this will be null the first time F1 is created.
// it will be populated once you replace fragment and provide bundle data
if (args != null) {
if (args.get("your_info") != null) {
// do what you want with restored information
}
}
}
So following on with my example: before I display F2 I save user data in the instance variable using a callback. Then I start F2, user fills in phone number and presses save. I use another callback in activity, collect this information and replace my fragment F1, this time it has bundle data that I can use.
#Override
public void onPhoneAdded(String phone) {
//replace fragment
F1 f1 = new F1 ();
Bundle args = new Bundle();
yourInfo.setPhone(phone);
args.putSerializable("you_info", yourInfo);
f1.setArguments(args);
getFragmentManager().beginTransaction()
.replace(R.id.fragmentContainer, f1).addToBackStack(null).commit();
}
}
More information about callbacks can be found here: https://developer.android.com/training/basics/fragments/communicating.html
Replace a Fragment using following code:
Fragment fragment = new AddPaymentFragment();
getSupportFragmentManager().beginTransaction().replace(R.id.frame, fragment, "Tag_AddPayment")
.addToBackStack("Tag_AddPayment")
.commit();
Activity's onBackPressed() is :
#Override
public void onBackPressed() {
android.support.v4.app.FragmentManager fm = getSupportFragmentManager();
if (fm.getBackStackEntryCount() > 1) {
fm.popBackStack();
} else {
finish();
}
Log.e("popping BACKSTRACK===> ",""+fm.getBackStackEntryCount());
}
Public void replaceFragment(Fragment mFragment, int id, String tag, boolean addToStack) {
FragmentTransaction mTransaction = getSupportFragmentManager().beginTransaction();
mTransaction.replace(id, mFragment);
hideKeyboard();
if (addToStack) {
mTransaction.addToBackStack(tag);
}
mTransaction.commitAllowingStateLoss();
}
replaceFragment(new Splash_Fragment(), R.id.container, null, false);
Perfect solution that find old fragment in stack and load it if exist in stack.
/**
* replace or add fragment to the container
*
* #param fragment pass android.support.v4.app.Fragment
* #param bundle pass your extra bundle if any
* #param popBackStack if true it will clear back stack
* #param findInStack if true it will load old fragment if found
*/
public void replaceFragment(Fragment fragment, #Nullable Bundle bundle, boolean popBackStack, boolean findInStack) {
FragmentManager fm = getSupportFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
String tag = fragment.getClass().getName();
Fragment parentFragment;
if (findInStack && fm.findFragmentByTag(tag) != null) {
parentFragment = fm.findFragmentByTag(tag);
} else {
parentFragment = fragment;
}
// if user passes the #bundle in not null, then can be added to the fragment
if (bundle != null)
parentFragment.setArguments(bundle);
else parentFragment.setArguments(null);
// this is for the very first fragment not to be added into the back stack.
if (popBackStack) {
fm.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
} else {
ft.addToBackStack(parentFragment.getClass().getName() + "");
}
ft.replace(R.id.contenedor_principal, parentFragment, tag);
ft.commit();
fm.executePendingTransactions();
}
use it like
Fragment f = new YourFragment();
replaceFragment(f, null, boolean true, true);
Calling the Fragment lifecycle methods properly and using onSavedInstanceState() can solve the problem.
i.e Call onCreate(), onCreateView(), onViewCreated() and onSavedInstanceState() properly and save Bundle in onSaveInstanceState() and resotre it in onCreate() method.
I don't know how but it worked for me without any error.
If anyone can explain it will very much appreciated.
public class DiagnosisFragment extends Fragment {
private static final String TITLE = "TITLE";
private String mTitle;
private List mList = null;
private ListAdapter adapter;
public DiagnosisFragment(){}
public DiagnosisFragment(List list, String title){
mList = list;
mTitle = title;
}
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if(savedInstanceState != null){
mList = savedInstanceState.getParcelableArrayList(HEALTH_ITEMS);
mTitle = savedInstanceState.getString(TITLE);
itemId = savedInstanceState.getInt(ID);
mChoiceMode = savedInstanceState.getInt(CHOICE_MODE);
}
getActivity().setTitle(mTitle);
adapter = (ListAdapter) new HealthAdapter(mList, getContext()).load(itemId);
}
#Nullable
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.diagnosis_fragment, container, false);
}
#Override
public void onViewCreated(#NonNull View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
ListView lv = view.findViewById(R.id.subLocationsSymptomsList);
lv.setAdapter(adapter);
}
#Override
public void onSaveInstanceState(#NonNull Bundle outState) {
outState.putParcelableArrayList(HEALTH_ITEMS, (ArrayList) mList);
outState.putString(TITLE, mTitle);
}
}
For who has looking for solution :
#Override
public void onDestroyView() {
Bundle savedState=new Bundle();
// put your data in bundle
// if you have object and want to restore you can use gson to convert it
//to sring
if (yourObject!=null){
savedState.putString("your_object_key",new Gson().toJson(yourObject));
}
if (getArguments()==null){
setArguments(new Bundle());
}
getArguments().putBundle("saved_state",savedState);
super.onDestroyView();
}
and in onViewCreated() method :
Bundle savedState=null;
if (getArguments()!=null){
savedState=getArguments().getBundle("saved_state");
}
if (savedState!=null){
// set your restored data to your view
}

Why is onCreateView in Fragment called twice after device rotation in Android?

I have simple activity and fragment transaction. What i noticed that on configuration changes oncreateView of Fragment is called twice. Why is this happening?
Activity Code Here :
#Override
protected void onCreate(Bundle savedInstanceState) {
System.out.println("Activity created");
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
FragmentManager manager = getSupportFragmentManager();
BlankFragment fragment = new BlankFragment();
addFragmentToActivity(manager,
fragment,
R.id.root_activity_create
);
}
public static void addFragmentToActivity (FragmentManager fragmentManager,
Fragment fragment,
int frameId)
{
FragmentTransaction transaction = fragmentManager.beginTransaction();
transaction.replace(frameId, fragment);
transaction.commit();
}
Fragment Code Here :
public class BlankFragment extends Fragment {
public BlankFragment() {
// Required empty public constructor
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_blank, container, false);
}
}
On first load onCreateView() is called once
But onRotation onCreateView() is called twice
why ?
Because of this transaction.replace(frameId, fragment); Really? Yes,I mean because of fragment .You already have one fragment onFirst load, When you rotate onCreate() will be called once again, so now fragment manager has old fragment ,so it methods will execute(once),and next you are doing transaction replace() which will remove old fragment and replace it with new once and again(onCreateView() will be called for second time). This is repeating for every rotation.
If you use transaction.add(frameId, fragment,UNIQUE_TAG_FOR_EVERY_TRANSACTION) you would know the reason. for every rotatation, no.of onCreateView() calls will increase by 1. that means you are adding fragments while not removing old ones.
But solution is to use old fragments.
in onCreate()of activity
val fragment = fragmentmanager.findFrgmentByTag("tag")
val newFragment : BlankFragment
if(fragment==null){
newFragment = BlankFragment()
}else{
newFragment = fragment as BlankFragment()
}
//use newFragment
Hope this solves confusion
Android automatically restores the state of its views after rotation. You don't have to call addFragmentToActivity again after rotation. The fragment will automatically be restored for you!
In your case, it happens twice because:
1. Android restores the fragment, its onCreateView is called
2. You replace the restored fragment with your own fragment, the oncreateview from that fragment is called too
do this:
if (savedInstanceState == null)
{
addFragmentToActivity(manager, fragment, R.id.test);
}

How can I recreate a fragment?

I'm using a widget called SwipeRefreshLayout, to refresh my fragment when someone pushes the view.
To recreate the activity I have to use:
SwipeRefreshLayout mSwipeRefreshLayout;
public static LobbyFragment newInstance() {
return new LobbyFragment();
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
final View view = inflater.inflate(R.layout.fragment_lobby, container, false);
receiver = new MySQLReceiver();
rlLoading = (RelativeLayout) view.findViewById(R.id.rlLoading);
gvLobby = (GridView) view.findViewById(R.id.gvLobby);
updateList();
mSwipeRefreshLayout = (SwipeRefreshLayout) view.findViewById(R.id.mSwipeRefreshLayout);
mSwipeRefreshLayout.setColorSchemeResources(R.color.pDarkGreen, R.color.pDarskSlowGreen, R.color.pLightGreen, R.color.pFullLightGreen);
mSwipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
#Override
public void onRefresh() {
getActivity().recreate();
}
});
return view;
}
But I don't want to recreate the full activity that contains the view pager, I would like to recreate the fragment. How can I do that?
I'm using .detach() and .attach() for recreating the fragment.
ATTACH
Re-attach a fragment after it had previously been deatched from the UI with detach(Fragment). This causes its view hierarchy to be re-created, attached to the UI, and displayed.
DETACH
Detach the given fragment from the UI. This is the same state as when it is put on the back stack: the fragment is removed from the UI, however its state is still being actively managed by the fragment manager. When going into this state its view hierarchy is destroyed.
HOW I USE IT
getFragmentManager()
.beginTransaction()
.detach(LobbyFragment.this)
.attach(LobbyFragment.this)
.commit();
You can use :
getSupportFragmentManager().beginTransaction()
.replace(R.id.container, LobbyFragment.newInstance()).commit();
To recreate your fragment
Note:getSupportFragmentManager() is if you are using support fragment and AppCompatActivity , if you are using framework fragment class you need to use getFragmentManager()
If you're using Navigation Component, you can use this:
findNavController().navigate(
R.id.current_dest,
arguments,
NavOptions.Builder()
.setPopUpTo(R.id.current_dest, true)
.build()
)
This lets NavController pop up the current fragment and then navigate to itself. You get a new Fragment and fragment ViewModel also gets recreated.
For Kotlin Lover
if you want to recreate fragment you should dettach() fragment then attach() fragment
please follow this step
setp : 1 , first create a method recreateFragment() on your activity class
fun recreateFragment(fragment : Fragment){
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.N){
supportFragmentManager.beginTransaction().detach(fragment).commitNow()
supportFragmentManager.beginTransaction().attach(fragment).commitNow()
}else{
supportFragmentManager.beginTransaction().detach(fragment).attach(fragment).commitNow()
}
}
step : 2 , then call this method on your fragment to recreate this fragment
suppose A Button click then recreate this fragment
button.setOnClickListener {
(activity as yourActivity).recreateFragment(this)
}
If you want to refresh from activity then use:
getSupportfragmentmanager()
.begintransaction
.detach(fragment)
.attach(fragment)
.addtobackstack(null)
.commit();
and if you are in fragment already then use:
public class MyDetailFragment extends Fragment {
....
private void refreshFragment(){
getFragmentManager()
.beginTransaction()
.detach(this)
.attach(this)
.addToBackStack(null)
.commit();
}
...
}
who use Navigation Component !! :
just put a self destination .
<action
android:id="#+id/action_piecesReferenceCount_self"
app:destination="#id/piecesReferenceCount" />
Navigation.findNavController(myview).navigate(R.id.action_piecesReferenceCount_self);
Using the method from Ciardini I got errors sometimes. This works always:
FragmentTransaction ft = getFragmentManager().beginTransaction();
if (Build.VERSION.SDK_INT >= 26) {
ft.setReorderingAllowed(false);
}
ft.detach(this).attach(this).commitAllowingStateLoss();
I had to use 2 transactions for the fragment to reload its content list:
FragmentTransaction ftr = getSupportFragmentManager().beginTransaction();
ftr.detach(localCurrentPrimaryItem)
.commit();
FragmentTransaction ftr2 = getSupportFragmentManager().beginTransaction();
ftr2.attach(localCurrentPrimaryItem)
.commit();
In my case, I had a fragment that needed to be recreated when I clicked on a button, what I did was the following in the onCreateView of the fragment (MyFragmentClass) class:
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
...
Button annuler = (Button) rootView.findViewById(R.id.buttonAnnulerCreation);
annuler.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
getParentFragmentManager().beginTransaction().replace(R.id.fragmentLayout, new MyFragmentClass()).commit();
}
}); }
Define a Kotlin extension function:
/**
* Recreate a fragment without recreating any associated fragment view model. This is useful if initially some work needs
* to be done to set up the data for a fragment. At the start the layout shows a "working" fragment state. When the work completes
* the fragment view model is set to indicate the data is available, and this triggers a different layout to be inflated.
*
* This causes [Fragment.onDestroy] followed by [Fragment.onViewCreated] to be called (but not [Fragment.onCreate]).
*
* For background see [Stackoverflow](https://stackoverflow.com/questions/39296873/how-can-i-recreate-a-fragment)
*/
fun Fragment.recreateFragment() {
val fragment = this
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
parentFragmentManager.beginTransaction().detach(fragment).commitNow()
parentFragmentManager.beginTransaction().attach(fragment).commitNow()
} else {
parentFragmentManager.beginTransaction().detach(fragment).attach(fragment).commitNow()
}
}

Can I navigate from one fragment (fragment1) to another (fragment2) through a button which is on fragment1?

I am having trouble to navigate from one fragment to another through a button in my android application. I have considered several questions about this issue but the solutions provided are not solving my problem. Here is my code and I don't know what I am doing wrong.
public class fragment_profile extends Fragment {
TextView txtFname, txtLname, txtGender, txtAge, txtPhone, txtEmail;
Button btImages, btVideos;
ImageButton btProfilePic;
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_profile, container, false);
btProfilePic = (ImageButton)rootView.findViewById(R.id.ProfilePic);
txtFname = (TextView)rootView.findViewById(R.id.tvFName);
txtLname = (TextView)rootView.findViewById(R.id.tvLName);
txtGender = (TextView)rootView.findViewById(R.id.tvGender);
txtAge = (TextView)rootView.findViewById(R.id.tvAge);
txtPhone = (TextView)rootView.findViewById(R.id.tvPhone);
txtEmail = (TextView)rootView.findViewById(R.id.tvEmail);
btImages = (Button)rootView.findViewById(R.id.btnImages);
btVideos = (Button)rootView.findViewById(R.id.btnVideos);
//The code to replace fragment is not good, the clicklistener is working fine as I have tested it with a toast message
btImages.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
//An object of the fragment tree is created
fragmentImages ImageGallery = new fragmentImages();
//The fragment is finally added
fragmentTransaction.replace(R.id.fragment_profile, ImageGallery, "Image Gallery").commit();
//Set title of action bar = title of fragment
getActivity().setTitle(getTag());
}
});
btVideos.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
//An object of the fragment tree is created
fragmentVideos VideoGallery = new fragmentVideos();
//The fragment is finally added
fragmentTransaction.replace(R.id.fragment_profile, VideoGallery, "Video Gallery").commit();
//Set title of action bar = title of fragment
getActivity().setTitle(getTag());
}
});
return rootView;
}
}
The issue is with the code inside the onClickListener. Can anyone tell me what I am doing wrong? Thank you.
You want to use the supportFragmentManager. Within your onClickListener(s), make your transaction this:
//An object of the fragment tree is created
fragmentImages ImageGallery = new fragmentImages();
getActivity().getSupportFragmentManager().beginTransaction().replace(R.id.fragment_profile, ImageGallery).commit();
//rest of your setting the activity title below
It is also best practise to make write your classes like FragmentImages and then your variables as imageGallery for example or even fragmentImages so you know what the object is.
I think you have to add your fragment_profile fragment to the backstack so you can recall it afterwards after you have triggered the other fragments. Try the following:
fragmentTransaction.replace(R.id.fragment_profile, ImageGallery, "Image Gallery").addToBackStack(null).commit();
Same with the latter:
fragmentTransaction.replace(R.id.fragment_profile, VideoGallery, "Video Gallery").addToBackStack(null).commit();
I got the information from here: Fragment Replacing Existing Fragment

Populate ListView only once on fragment with SlidingMenu android

I'm new on android developing, and I'm developing an app with SlidingMenu library from jfeinstein10, and i'm listing some top rated data on main screen...
Now i'm doing this by getting data from SQLite and putting on a ListView inside a fragment activity
HomeFragment
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.home_fragment, container, false);
new ManageSQLiteResponse((ListView)rootView.findViewById(R.id.lv_top_rate)).execute();
return rootView;
}
ManagerSQLiteResponse call:
public void updateList(ListView listView){
DietsListAdapter adapter = new DietsListAdapter(getActivity(), R.layout.single_top_rate, dietList);
listView.setAdapter(adapter);
}
And every time i switch the fragment by selecting on SlidingMenu, all the ListView is populated again, and it causes some lag, and the menu won't open or close smoothly...
So, is there some way to run AsyncTask.execute() and populate the ListView only once, and not every time the fragment is created? by this i think it will stop lagging the SlidingMenu
TVM
use this method to add fragment in your container layout.
public void replaceFragment(Fragment fragment) {
String backStateName = fragment.getClass().getName();
FragmentManager manager = getSupportFragmentManager();
FragmentTransaction ft = manager.beginTransaction();
ft.setCustomAnimations(android.R.anim.slide_in_left, android.R.anim.slide_out_right);
Fragment fragmentPopped = manager.findFragmentByTag(backStateName) ;
if ( fragmentPopped != null )
manager.popBackStack(backStateName, 0);
else
ft.replace(R.id.container, fragment);
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
ft.addToBackStack(backStateName);
ft.commit();
}
and look make a condition in code like this, if
if ( adapter.getCount() > 0 )
{
// dont add listview.setadapter() method here.
}
elsse
{
// load data in listview.
}

Categories

Resources