shared element transition replace the fragment, so I can't add it to the backstack and call popbackstack when the back arrow button is pressed.
I have a main activity with inside a mainfragment, the main fragment handle a table layout so each tab is a fragment with inside a recycler view, when a recycler view element is clicked the shared element transition start to a new fragment that shows element details.
This is the adapter, where the element is clicked:
holder.image.setTransitionName("transition" + holder.getAdapterPosition());
if (fragment instanceof tab1_anime) {
((tab1_anime) fragment).openShowElementFragment(holder.getAdapterPosition(), v.findViewById(R.id.main_image));
}
This is the openShowElementFragment inside my tab fragment:
public void openShowElementFragment(int position, View view) {
AddElement element = anime_list.get(position);
ShowElementFragment showElementFragment = new ShowElementFragment();
Bundle bundle = new Bundle();
bundle.putString("transitionName", "transition" + position);
bundle.putSerializable("element", element);
bundle.putInt("position", position);
bundle.putInt("from", 0);
showElementFragment.setArguments(bundle);
((MainActivity) context).showFragmentWithTransition(this, showElementFragment, "showElementFragment", view, "transition" + position);
}
this is the openshowelementfragment function called in the previous code block:
public void showFragmentWithTransition(Fragment current, Fragment newFragment, String tag, View sharedView, String sharedElementName) {
FragmentManager fragmentManager = getSupportFragmentManager();
// check if the fragment is in back stack
boolean fragmentPopped = fragmentManager.popBackStackImmediate(tag, 0);
if (fragmentPopped) {
} else {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
current.setSharedElementReturnTransition(TransitionInflater.from(this).inflateTransition(R.transition.default_transition));
current.setExitTransition(TransitionInflater.from(this).inflateTransition(R.transition.default_transition));
newFragment.setSharedElementEnterTransition(TransitionInflater.from(this).inflateTransition(R.transition.default_transition));
newFragment.setEnterTransition(TransitionInflater.from(this).inflateTransition(R.transition.default_transition));
}
fragmentManager.beginTransaction()
.replace(R.id.fragmentHolder, newFragment)
.addToBackStack(null)
.addSharedElement(sharedView, sharedElementName)
.commit();
}
}
and this is the backarrow button:
back_arrow.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
getSupportFragmentManager().popBackStack();
}
});
If I try to add the new fragment instead of replace the old one, then there is not animations at all.
If I try to replace the old fragment with the new one and using anyway the addtobackstack(null) then the shared element transition works from start to end BUT the fragment at the end is without data, empty:
I tried also:
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.fragmentHolder, new MainFragment())
.addToBackStack(null)
.commit();
but in this way the shared element transition doesn't work on exit:
I think your problem is related to exit transition as you get solution of empty list, now for exit transition effect check below code.
public void showFragmentWithTransition(Fragment current, Fragment newFragment, String tag, View sharedView, String sharedElementName) {
FragmentManager fragmentManager = getSupportFragmentManager();
// check if the fragment is in back stack
boolean fragmentPopped = fragmentManager.popBackStackImmediate(tag, 0);
if (fragmentPopped) {
} else {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
current.setSharedElementReturnTransition(TransitionInflater.from(this).inflateTransition(R.transition.default_transition));
current.setExitTransition(TransitionInflater.from(this).inflateTransition(R.transition.default_transition));
newFragment.setSharedElementEnterTransition(TransitionInflater.from(this).inflateTransition(R.transition.default_transition));
newFragment.setEnterTransition(TransitionInflater.from(this).inflateTransition(R.transition.default_transition));
newFragment.setExitTransition(TransitionInflater.from(this).inflateTransition(R.transition.default_transition));
newFragment.setSharedElementReturnTransition(TransitionInflater.from(this).inflateTransition(R.transition.default_transition));
}
getSupportFragmentManager().beginTransaction()
.addSharedElement(sharedElement, transitionName)
.replace(R.id.container, newFragment)
.addToBackStack(null)
.commit();
}
}
Here I add two new lines for exit transitions.
newFragment.setExitTransition(TransitionInflater.from(this).inflateTransition(R.transition.default_transition));
newFragment.setSharedElementReturnTransition(TransitionInflater.from(this).inflateTransition(R.transition.default_transition));
If its not work then follow below link, I think it will help you.
https://medium.com/#bherbst/fragment-transitions-with-shared-elements-7c7d71d31cbb
Your so called sharedElementName is actually a transitionName.
a) The reference example doesn't directly manipulate the backstack:
Android Fragment Transitions: RecyclerView to ViewPager & Blog.
b) Manual transactions might require another order of method calls:
getSupportFragmentManager().beginTransaction()
.addSharedElement(sharedElement, transitionName)
.replace(R.id.container, newFragment)
.addToBackStack(null)
.commit();
Also see this example, how alternate transitions could be be set, compared to:
TransitionInflater.from(this).inflateTransition(R.transition.default_transition)
Whatever R.transition.default_transition may be.
It may look strange when using the same transition for both ways, instead of enter/exit transitions, alike NavAction would feature. FragmentNavigator.Extras could be used to apply enter/exit transitions, when using navigation component; this also can be combined with ActionBar. Option a) is probably less complicated. b) something alike this Kotlin example might make more sense.
It's pointless to build clunky navigation FX when enter/exit transitions would be supported out-of-the-box. Maybe consider loading with Glide, when loading images.
Assume the following build.gradle; there's no need to reinvent the wheel.
dependencies {
androidTestImplementation "androidx.navigation:navigation-testing:2.5.3"
implementation 'androidx.navigation:navigation-runtime:2.5.3'
implementation 'androidx.navigation:navigation-fragment:2.5.3'
implementation 'androidx.navigation:navigation-ui:2.5.3'
annotationProcessor 'com.github.bumptech.glide:compiler:4.14.2'
implementation 'com.github.bumptech.glide:glide:4.14.2'
}
Then Navigation with FragmentNavigator.Extras might rather be the current method:
Bundle navArgs = new Bundle();
navArgs.putInt("position", position);
FragmentNavigator.Extras extras = new FragmentNavigator.Extras.Builder()
.addSharedElement(sharedElement, transitionName)
.build();
Navigation.findNavController(view).navigate(
R.id.details, // ID of Nav destination
navArgs, // Bundle of args
null, // NavOptions
extras);
One can also define transitionName in XML <action/> nodes.
Related
I'm using Bottom-navigation with 3 fragments. On Home-fragment I'm requesting API to fetch data and show in recycler-view my problem is whenever I switch fragments and come again to Home-fragment it's recreating the layout and again its fetch data from API I want to load only one time when app launch
This is where I call API in the fragment
#Nullable
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.home_fragment, container, false);
// return inflater.inflate(R.layout.home_fragment, container, false);
sharedPrefManager = new SharedPrefManager(getActivity());
locationTrack = new LocationTrack(getActivity());
buildGoogleApiClient();
fusedLocationClient = LocationServices.getFusedLocationProviderClient(getActivity());
getUserLatLng();
setUp(view);
netWorkCall();
return view;
}
HomeActivity to load home-fragment by default
if (savedInstanceState == null) {
Fragment fragment = null;
fragment = new HomeFragment();
if (fragment != null) {
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.add(R.id.content_frame, fragment, "home").addToBackStack("Home");
ft.commit();
}
}
bottom-navigation click listener
private void displaySelectedScreen(int itemId) {
Fragment fragment = null;
switch (itemId) {
case R.id.action_home:
fragment = new HomeFragment();
break;
case R.id.action_profile:
if (sharedPrefManager.getAuthority()) {
fragment = new ProfileFragment();
} else {
SDConstant.switchActivity(this, LoginScreen.class);
}
break;
case R.id.action_calculator:
if (sharedPrefManager.getAuthority()) {
fragment = new CalculatorFragment();
} else {
SDConstant.switchActivity(this, LoginScreen.class);
}
break;
}
//replacing the fragment
if (fragment != null) {
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.replace(R.id.content_frame, fragment, "home").addToBackStack("Home");
ft.commitAllowingStateLoss();
//ft.commitNow();
}
}
}
Please guide me solution How to solve this re-creation API call
First off, perform this calls inside onViewCreated instead of onCreateView:
sharedPrefManager = new SharedPrefManager(getActivity());
locationTrack = new LocationTrack(getActivity());
buildGoogleApiClient();
fusedLocationClient = LocationServices.getFusedLocationProviderClient(getActivity());
getUserLatLng();
setUp(view);
netWorkCall(); //this call has to be removed as explained later
Why? Because onCreateView should just return the fragments UI. OnViewCreated is called directly afterwards and this is the place to do some stuff.
No to get to your problem. The best way for you to solve this would be using a ViewModel from the architecture components in conjunction with LiveData.
You can read more on this topic here.
Your implementation could look like this (adapted the example from the link):
public class MyViewModel extends ViewModel {
private MutableLiveData<List<User>> users;
public LiveData<List<User>> getUsers() {
if (users == null) {
users = new MutableLiveData<List<User>>();
loadUsers();
}
return users;
}
private void loadUsers() {
// Do an asynchronous operation to fetch users.
}
}
In your onViewCreated you can do something lik this:
MyViewModel model = new ViewModelProvider(getViewLifecycleOwner()).get(MyViewModel.class);
model.getUsers().observe(getViewLifecycleOwner(), users -> {
// update UI
});
Don't feel overwhelmed by this. What it does in reality is relatively simple. The ViewModel survives configuration changes. Meaning if you rotate your phone the loadUsers request won't be invoked again - same with switching between bottom navigation tabs. Of course If the fragment is destroyed, the ViewModel does also get destroyed (the function onCleared is invoekd then).
The LiveData users is just a fancy name for a simple observer. Mutable means that you can set a value, getUsers() returns a non mutable LiveData, this means you can only read its value. When you set a value to a LiveData object, it's subscriber will be notified. You can imagine it like the LiveData is an interface implmented by your Fragment which is using the ViewModel. And this interfaces function is invoked once you have fetched data. More info on dealing with LiveData is explained here you should definitely check it out.
So the ViewModel will invoke loadUsers once you start observing getUsers if the Users have not been loaded yet. If they have been loaded, the current value is returned.
Oh and to add LiveData and ViewModel -> it's explained here :)
Hope this was helpful. I know it is alot but trust me it is worth investing the time!
I've been stuck on this for a while now and will appreciate any guidance on this. Also not sure if my title is accurate. Inside my adapter I create a new fragment:
Adapter code:
cardAdoptDetailsFrag nextFrag = new cardAdoptDetailsFrag();
android.support.v4.app.Fragment callingFrag = ((FragmentActivity)context).getSupportFragmentManager().findFragmentByTag("TagFeedFragment");
FragmentTransaction ft = ((FragmentActivity)context).getSupportFragmentManager().beginTransaction();
ft.hide(callingFrag);
ft.add(R.id.fram, nextFrag,"cardAdoptDetailsFrag");
ft.addToBackStack("TagFeedFragment");
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
ft.commit();
Then inside my Main Activity where I manage all the fragments, I'm trying to check if the "cardAdoptDetailsFrag" isAdded. For some reason I cannot do it as per the below.
Get Fragment by tag. The below is where it fails with null object reference.
Fragment frag = getSupportFragmentManager().findFragmentByTag("cardAdoptDetailsFrag");
Log.d(TAG, "Check if added: "+frag.isAdded());
Now I know I can just add it inside a method and do a null check and return a boolean, but I know I'm doing something wrong here. Because with my other fragments the isAdded and remove works, but they get initiated inside the Main Activity where "cardAdoptDetailsFrag" gets initiated inside the adapter.
Example of Main Activity:
public class MainActivity extends AppCompatActivity implements adoptFeedFragment.OnFragmentInteractionListener,
lostAndFoundFragment.OnFragmentInteractionListener,
servicesFragment.OnFragmentInteractionListener,
userMenuFragment.OnFragmentInteractionListener,
showUserAdoptPostsFrag.OnFragmentInteractionListener{
adoptFeedFragment adoptFeedFragment;
lostAndFoundFragment lostAndFoundFragment;
servicesFragment servicesFragment;
userMenuFragment userMenuFragment;
....
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
adoptFeedFragment = new adoptFeedFragment();
lostAndFoundFragment = new lostAndFoundFragment();
servicesFragment = new servicesFragment();
userMenuFragment = new userMenuFragment();
....
//Here I can do for example:
adoptFeedFragment.isAdded(); //Will simply return a boolean.
//Or I can do a remove:
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.remove(adoptFeedFragment);
//But I cannot do the below:
Fragment frag = getSupportFragmentManager().findFragmentByTag("cardAdoptDetailsFrag");
Log.d(TAG, "frag.isAdded(): "+frag.isAdded());
*****Edited Post. Ok lets say the frag is added. Why can I not remove it using the below.
public boolean isAdoptDetailsFragAdded() {
Fragment frag = getSupportFragmentManager().findFragmentByTag("cardAdoptDetailsFrag");
if(frag == null){
return false;
} else {
return true;
}
}
public Fragment getAdoptDetailsFrag() {
return getSupportFragmentManager().findFragmentByTag("cardAdoptDetailsFrag");
}
//I'm unable to remove the fragment using the below:
Log.d(TAG, "showFeedFragment: "+isAdoptDetailsFragAdded()); <--returns true
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
if(isAdoptDetailsFragAdded()) {
ft.remove(getAdoptDetailsFrag());
}
ft.commit();
//Now I check again wether it is still added. And still returns true even though I just removed it.
Log.d(TAG, "showFeedFragment: "+isAdoptDetailsFragAdded()); <-- Still returns true.
}
This is my fragment adding or replacing method which i am using in 13 projects.
You can use it and get rid of managing fragments.
/**
* 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.main_frame_container, parentFragment, tag);
ft.commit();
fm.executePendingTransactions();
}
Here R.id.main_frame_container is FrameLayout in activity layout on which fragment is placed.
Here findFragmentByTag() is returning null when you do getSupportFragmentManager().findFragmentByTag("cardAdoptDetailsFrag").
findFragmentByTag() will return null in two cases.
If fragment cardAdoptDetailsFrag is not yet attached to view (you did not call add or replace for this fragment)
You added that fragment, but you are using different TAG, while searching that fragment.
Make sure if non of the case in your problem.
Now the solution is, you can modify your logic, like
Fragment frag = getSupportFragmentManager().findFragmentByTag("cardAdoptDetailsFrag");
if (frag != null && frag.isAdded()) {
// Do you task. Fragment is added
} else {
// Fragment is not added yet
}
I did not went through the long post but I read only this part.
Now I check again wether it is still added. And still returns true even though I just removed it.
And the reason behind that is FragmentTransaction.commit is not synchronous.
Read the docs for commit method:
*Schedules a commit of this transaction. The commit does
* not happen immediately; it will be scheduled as work on the main thread
* to be done the next time that thread is ready.*
If you need that to be synchronous, use commitNow instead.
I have a dummy content list containing 3 items but I want each item to open its own activity rather than passing data to the detail fragment. How can I replace the master fragment with a list fragment of my own when a particular list item is clicked? Is it possible to use the if (position == x) statement for each of my items? The code below replaces the detail fragment but I want to replace the master fragment instead. I have not seen any tutorials on this as all the ones I've come across are associated with replacing the detail fragment, NOT the master fragment.
#Override
public void onItemSelected(String id) {
if (mTwoPane) {
Bundle arguments = new Bundle();
arguments.putString(ItemDetailFragment.ARG_ITEM_ID, id);
ItemDetailFragment fragment = new ItemDetailFragment();
fragment.setArguments(arguments);
getSupportFragmentManager().beginTransaction()
.replace(R.id.item_list, fragment)
.commit();
} else {
Intent detailIntent = new Intent(this, ContinentsListActivity.class);
detailIntent.putExtra(ItemDetailFragment.ARG_ITEM_ID, id);
startActivity(detailIntent);
}
}
You may want to use View.setTag() every time you add an item to your list, so later you can retrieve this tag with View.getTag() and identify the selected item.
I have three fragments which are bound to Tabs as you swipe from TabA to TabB to TabC and it respectively loads FragA, FragB and FragC using ViewPager(Hope you understand this part). This swipe Tabs works just fine but I have a few fixed buttons attached to the bottom of the layout and when each button is clicked i want to load a new fragment to replace any of the Fragment A, B or C. The replacement works fine but when i return to the replaced/previous Fragment all the UI components on that layout completely disappears and it does not indicate if it has been paused, stopped or destroyed. Heres the Code:
Replacing previous Fragment Code:
if(actionBar.isShowing())
{
actionBar.hide();
}
FragmentManager fms = getSupportFragmentManager();
FragmentTransaction fts = fms.beginTransaction();
search s = new search();
if(getCurrentDisplayFragment() == Type1)
{
setcurrentItem(viewPager.getCurrentItem());
Fragment frgs = mAdapter.getItem(viewPager.getCurrentItem());
viewPager.removeViewAt(viewPager.getCurrentItem());
frgs.setUserVisibleHint(false);
Log.i("if say", "Not Responding");
}
fts.replace(R.id.hoster, s);
fts.addToBackStack("search");
fts.commit();
setCurrentDisplayFragment(s,Type2);
The setCurrentDisplayFragment(s,Type2); i built that code to help indicate which fragment is being replaced and what measures to take. S is a Fragment and Type2 is a string
Thus when the back button is pressed i want to return back to any of the Fragments A, B or C that was previously replaced.
Here is the code:
#Override
public void onBackPressed() {
// TODO Auto-generated method stub
//super.onBackPressed();
Log.i("event in back","I have been presssed ");
if(getCurrentDisplayFragment() == Type2)
{
actionBar.show();
FragmentManager fm = getSupportFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
fm.popBackStack();
ft.remove(getCurrentDisplayFragment(Type2));
ft.commit();
viewPager.setCurrentItem(getcurrentItem());
actionBar.setSelectedNavigationItem(getcurrentItem());
Fragment frags = mAdapter.getItem(viewPager.getCurrentItem());
frags.setUserVisibleHint(true);
setCurrentDisplayFragment(frags, Type1);
}
else
{
}
}
Though some variable names and method declaration are not shown but you should get the picture of what am doing because all declarations have been done. its just The Replacement and retaining of their UI states thats giving me a problem.
Why are you complicating this so much? If you want to replace any of the fragments in the view pager, then just replace the view pager. It would be a lot easier if you would have a fragment holding the view pager with the tabs and when the bottom buttons are pressed, you just replace the fragment holding the view pager with the new fragment. In this way you won't need to do anything when the back button will be pressed.
I'm using Fragment in my android app. At the moment I have 2 fragments being displayed in split view style, lets call the Fragment A and Fragment B. Fragment B gets changed frequently and there are many different types of Fragment it could get changed to.
I'm looking to call a method on Fragment B. This method exists in most but not all of the types of Fragments that could be displayed.
Here is what I have:
FragmentManager fm = getSupportFragmentManager();
Fragment frag = fm.findFragmentByTag("ContentFrag");
if (frag != null) {
if (frag.getClass().equals(ExampleFragment1.class))
((ExampleFragment1)frag).refresh();
else if (frag.getClass().equals(ExampleFragment2.class))
((ExampleFragment2)frag).refresh();
else if (frag.getClass().equals(ExampleFragment3.class))
((ExampleFragment3)frag).refresh();
}
And here is what I would like:
FragmentManager fm = getSupportFragmentManager();
Fragment frag = fm.findFragmentByTag("ContentFrag");
if (frag != null) {
frag.refresh();
}
Is there any way of achieving this or will I have to write a load of if statements?
Thanks
You can make your Fragments implement for example a Refreshable interface and you can check for it.
if(frag instanceof Refreshable)
((Refreshable)frag).refresh();
}
This is not pretty but prettier than the above code.
you could potentially use instanceof keyword to see if the fragment is an instance of one of these special fragments. Is this method that's only in some fragments something you made yourself or part of the API?
If it's one you made yourself have those fragments implement an interface called something like RefreshableFragment. This interface would contain said method, then all you have to do is:
if(frag!=null && frag instanceof RefreshableFragment)
frag.refresh();
the solution is to create a parent class named RefreshableFragment, that has refresh() method:
public class RefreshableFragment extends Fragment {
public void refresh();
}
all your fragment classes should extend this class:
public class ExampleFragment1 extends RefreshableFragment {
public void refresh() {
// implementation
}
}
then you can use the refreshed fragment in your code:
FragmentManager fm = getSupportFragmentManager();
RefreshableFragment frag = (RefreshableFragment)fm.findFragmentByTag("ContentFrag");
if (frag != null) {
frag.refresh();
}