How to instantiate fragment class using class name instead of index - java

I have two fragment class named SessionTab and BillingTaband i am trying to create instance of those class using
SessionTab sessionTab = (SessionTab) getSupportFragmentManager().getFragments().get(1);
but sometimes index for those classes are reversed and then it causes ClassCastException
How can i get instance of those fragment class by passing class name instead of index or any way to make sure that index of those class stays the same everytime so it doesn't cause ClassCastException

Use one of this methods : findFragmentById() and findFragmentByTag() methods.
Reference : https://developer.android.com/reference/android/app/FragmentManager.html#findFragmentById(int)
Update :
ClassCastException is invoked when you are not casting the appropriate classes to one another. In your case, Your FragmentManager is returning different fragment than SessionTab, so the exception is thrown.
If you use findFragmentById() or findFragmentByTag() , then it will return the fragment exactly what you want, and exception will not be thrown.

Define a 'tag' for the Fragment while adding it like
getFragmentManager().beginTransaction().add(new Fragment(),"your_tag");
And while referencing it use
getFragmentManager().findFragmentByTag("your_tag");
In most cases, you would like to use YourFragment.class.getSimpleName() as your tag.

First of all, if you should understand that instance for any Fragment you can take from Java class api. Like below:
Class<?> class = Class.forName("example.package.BillingFragment");
Constructor<?> cons = class.getConstructor(BillingFragment.class);
BillingFragment object = (BillingFragment) cons.newInstance();
Code example show, how get Instance from any class in Java. But you talking a little bit other things. If I understand correct, you want to get Fragment from FragmentManager.
You can do it, in case if you already defined Fragment before! For example, you have base application flow, and then you want added Fragment. You can check FragmentManager if there are Fragments in stack. But in case of empty stack, you should manually add them:
String billingFragmentTag = BillingFragment.class.getSimpleName();
......
if (getFragmentManager.findFragmentByTag(billingFragmentTag) == null) {
BillingFragment fragment = new BillingFragment();
String billingFragmentTag = BillingFragment.class.getSimpleName();
FragmentTransaction fragTrans = getFragmentManager().beginTransaction();
fragTrans.add(fragment, billingFragmentTag).commit();
}
......
So after this, you can check if there your Fragment in stack and hook this active instance. This is correct and standard flow for using Fragments.
......
if (getFragmentManager.findFragmentByTag(billingFragmentTag) != null) {
BillingFragment fragment = getFragmentManager.findFragmentByTag(billingFragmentTag);
String billingFragmentTag = BillingFragment.class.getSimpleName();
FragmentTransaction fragTrans = getFragmentManager().beginTransaction();
fragTrans.add(fragment, billingFragmentTag).commit();
}
....
Welcome!

Related

Proper way to remove a child fragment from a FrameLayout with multiple sources

I have a FrameLayout which can be filled with different fragments based upon the user's choice on a RadioGroup. However, I am quite certain that the method I am using to remove such fragments is far from ideal and if possible, I would like some insights on how to make this properly.
How I am removing the fragments right now:
(...)
//Inside the body of the OnViewCreated
FragmentManager manager = getChildFragmentManager();
//RadioGroup listener to show a fragment based on the user's choice
radioGroup.setOnCheckedChangeListener((group, checkedId) -> {
//getCheckedRadio is only a method I created to get the index of the option chosen by the user
int index = getCheckedRadio(group);
switch (index) {
case 1:
getChildFragmentManager().beginTransaction().replace(R.id.child_fragment, new FragmentA()).commit();
break;
case 2:
getChildFragmentManager().beginTransaction().replace(R.id.child_fragment, new FragmentB()).commit();
break;
case 3:
getChildFragmentManager().beginTransaction().replace(R.id.child_fragment, new FragmentC()).commit();
break;
default:
removeFragments();
break;
}
(...)
//Method I use to remove the fragments
public void removeFragments() {
try {
FragmentA fragA = (FragmentA) manager.findFragmentById(R.id.child_fragment);
if (fragA != null)
manager.beginTransaction().remove(fragA).commit();
} catch (Exception e) {
try {
FragmentB fragB = (FragmentB) manager.findFragmentById(R.id.child_fragment);
if (fragB != null)
manager.beginTransaction().remove(fragB).commit();
} catch (Exception f) {
try {
FragmentC fragC = (FragmentC) manager.findFragmentById(R.id.child_fragment);
if (fragC != null)
manager.beginTransaction().remove(fragC).commit();
} catch (Exception g) {
Toast.makeText(getContext(), "Try Again", Toast.LENGTH_SHORT).show();
}
}
}
}
However, I know that this method is far from perfect because, should I call it at any point outside of radioGroup.SetOnCheckedChangeListener, it won't remove the fragment being exposed at the time. So my question is: Which is the correct way to remove a fragment from a FrameLayout that can hold different types of Fragments? And why my removeFragments() method works when used on this listener but not when called at other points in the code?
Which is the correct way to remove a fragment from a FrameLayout that can hold different types of Fragments?
First
No need to be bothered by what types of fragments that can be hosted by a FrameLayout placeholder; at the end of the day they are all Fragments (i.e. subclasses of the Fragment class).
Second
Not sure if there is a misconception of the fragment transaction concept; as the name of the method removeFragments() implies to remove multiple fragment(s) from a single placeholder at a time; and the logic of the method applies that to FragmentA, B, & C (i.e. there is a thought that more than one fragment can exist at a time); and this is not right; as at a time, a single placeholder/container can hold one and only one fragment.
This is something different than the back stack which can have multiple instances of the fragments according to the user navigation.
So, the removeFragments() actually need to be removeFragment() where only one fragment need to be removed at a time.
I guess that you thought that replacing a fragment keeps the old one in the placeholder, as per documentation:
Calling replace() is equivalent to calling remove() with a fragment in a container and adding a new fragment to that same container.
Third
There is no need to wrap the remove(fragment) into a try catch block; this method doesn't through any exception at all, so you will never get into the catch block; even if the fragment isn't the current fragment of the container, then it silently discard it.
So, the logic in removeFragments() can be simplified to:
public void removeFragment() {
Fragment fragment = manager.findFragmentById(R.id.child_fragment);
if (fragment != null)
manager.beginTransaction().remove(fragment).commit();
}
should I call it at any point outside of radioGroup.SetOnCheckedChangeListener
This depends on the logic/event you want to use to remove the fragment.

Getting data from fragment without loading the view

I have three fragments inside an activity. Three text views inside this activities. I have three API calls inside each of the fragments. I need the count of ArrayList created in the three fragments. I tried to make the API call in activity and tried to find out the ArrayList from there and passing it to fragment. But I don't know how to pass this type of ArrayList (Arraylist array) to fragment. I only need three integer values in the activity. So the questions are,
1) Please give me a way to pass value from three fragments to activity by actually loading the view of one fragment and loading the rest of the fragment without views.
2) Or give me a way to pass an ArrayList of model-class created at the activity to three fragments.
1 - Get the size of Array from fragment:
In this case you need to get the instance of the added fragment, and retrieve the values you need from it's global variables.
In fragment:
public List<MyObject> myList;
In Activity:
int size;
MyFragment fragment = (MyFragment)getSupportFragmentManager().findFragmentByTag("myFragmentTag");
if(fragment != null && is fragment.isAdded())
size = fragment.myList.size();
PS: in the above case don't forget to add a TAG to the fragment when you add it
2 - Pass the Array to fragment:
In this case, you need to make the Object Serializable, add it as an argument to the fragment that is about to be added, and then, when the fragment is added, within the fragment, retrieve the previously added Object
Make the Object Serializable and add it to the Arguments from Activity
In Activity:
public MySerializableObjectList myList;
Add the array to fragment in Activity:
MyFragment myFragment = new MyFragment();
fragmentManager = getSupportFragmentManager();
Bundle bundle = new Bundle();
bundle.putSerializable("myArrayTag", myList);
myFragment.setArguments(bundle);
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.add(R.id.fragment_container, myFragment,"myFragmentTag").commit();
In Fragment:
public MySerializableObjectList myList;
if(getArguments != null)
myList = (MySerializableObjectList)getArguments().getSerializable("myArrayTag");
List<MySerializableObject> myListObject;
if(myList != null)
myListObject = myList.getMySerializableObjectList();
putParcelableArrayList and getParcelableArrayList trough Bundle in the Fragment arguments.
Okey, lets see. In my opinion the best way to do it is query the info in the activity and passing to the fragment, it means, the second way you proposse.
to do this follow this steps
1) Obtain the List in activity onCreate method.
2) In the fragment, Create a variable and a method that recieve the object from the activity.
public class FormFragment extends Fragment {
List<Object> data;
public FormFragment() {
// Required empty public constructor
}
public void initFragment(List<Object> data){
this.data = data
}
}
3) After create the fragment, use this method to pass the info to the fragment
public class Activity extends AppCompat{
List<Object> data = //Query your data here
/....
FormFragment fragment = new FormFragment()
fragment.init(data)
}
and that is all, you can pass the info this way and works smoothly, also, you can use your data from the activity as you bless.

How to get a view from within Espresso to pass into an IdlingResource?

I essentially have a custom IdlingResource that takes a View a constructor argument. I can't find anywhere that really talks about how to implement it.
I'm trying to use this answer: https://stackoverflow.com/a/32763454/1193321
As you can see, it takes a ViewPager, but when I'm registering the IdlingResource in my test class, I'm not sure how I can get my view.
I've tried findViewById() and I've tried getting the currently running activity and then calling findViewById() on that, with no luck.
Anyone know what to do in this scenario?
Figured it out. To get the view to pass into an idling resource, all you have to do is take the member variable of your ActivityTestRule
For example:
#Rule
public ActivityTestRule<MainActivity> activityTestRule = new ActivityTestRule<>(
MainActivity.class);
and then just call getActivity().findViewById(R.id.viewId)
So the end result is:
activityTestRule.getActivity().findViewById(R.id.viewId);
The accepted answer works as long as a test is running in the same activity. However, if the test navigates to another activity activityTestRule.getActivity() will return the wrong activity (the first one). To address this, one can create a helper method returning an actual activity:
public Activity getCurrentActivity() {
final Activity[] currentActivity = new Activity[1];
InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
#Override
public void run() {
Collection<Activity> allActivities = ActivityLifecycleMonitorRegistry.getInstance()
.getActivitiesInStage(Stage.RESUMED);
if (!allActivities.isEmpty()) {
currentActivity[0] = allActivities.iterator().next();
}
}
});
return currentActivity[0];
}
And then it could be used as the following:
Activity currentActivity = getCurrentActivity();
if (currentActivity != null) {
currentActivity.findViewById(R.id.viewId);
}
If you are using ActivityScenarioRule from androidx.test.ext.junit.rules (since ActivityTestRule "will be deprecated and eventually removed from library in the future"), you can get your Activity instance and call findViewById method:
import androidx.test.ext.junit.rules.activityScenarioRule
import androidx.test.ext.junit.runners.AndroidJUnit4
#RunWith(AndroidJUnit4::class) {
#get: Rule
var testRule = activityScenarioRule<MainActivity>()
#Test
fun mainTestCase() {
testRule.scenario.onActivity { activity ->
val view = activity.findViewById<YourView>(R.id.view)
}
}
}
I haven't already used IdilingResources in Espresso, but did you saw these articles:
Espresso: Custom Idling Resource by Chiuki
Wait for it...a deep dive into Espresso's Idling Resources
Also please check official Android Docs: Idling Resources (reference)
To answer your question,
the best way to do it is passing in an instance of one of the Views into the class's constructor. Check: Calling findViewById() from outside an activity
another way is getting view by context. Check android - How to get view from context?
Here's an exmple taken from a link above:
Starting with a context, the root view of the
associated activity can be had by
View rootView = ((Activity)_context).Window.DecorView.FindViewById(Android.Resource.Id.Content);
In Raw Android it'd look something like:
View rootView = ((Activity)mContext).getWindow().getDecorView().findViewById(android.R.id.content)
Then simply call the findViewById on this
View v = rootView.findViewById(R.id.your_view_id);
This might be also useful: How to call getResources() from a class which has no context?
Hope it help

Access fragment method from fragment

I've a fragment register. this one has a view pager inside. I need every page to put a data into register hashmap variable . this what i've tried ..
this is a method that i want to access from every pages
public void addData(String key, String data){
Toast.makeText(this.getActivity(), data, Toast.LENGTH_LONG).show();
}
i've tried like :
FragmentManager fm = getFragmentManager();
fragment = (ArtistRegister)fm.findFragmentById(R.id.asd);
fragment.addData("asd", asd);
but it always return null pointer ..
First solution:
ArtistRegister artistRegister = ((ArtistRegister) getParentFragment());
artistRegister.addData("asd", "asd");
Second solution:
Create interface and pass it as argument for PagerAdapter constructor.
EDIT:
OP used getFragmentManager() instead of getChildFragmentManager() to initialize adapter, so he was constantly getting NullPointerExpception

How can I write a method with a fragment class as an argument that will allow me to create a new instance of that fragment?

The Goal
I'm trying to write a method that would replace the code that I use to swap fragments in order to keep copying and posting to a minimum (and to stay D.R.Y.)
The Problem
I get an error when I attempt to use the class that I passed in as an argument to create a new instance of that class.
The error occurs in this line of code, to the left of the operator (equal sign):
newFragmentClass new_fragment = newFragmentClass.newInstance();
The error it gives me is: "newFragmentClass cannot be resolved to a type".
Full Code
private void changeFragment(Class<?> newFragmentClass)
{
// Detect the current fragment
Fragment current_fragment = getSupportFragmentManager().findFragmentById(R.id.fragment_container);
// Create a new instance of the given class, edit later to gracefully handle errors
newFragmentClass new_fragment = newFragmentClass.newInstance();
// Switch to the new fragment
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.detach(current_fragment);
transaction.replace(R.id.fragment_container, new_fragment);
transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
transaction.commit();
// Change the tab background to indicate that the tab is active, reset backgrounds for any other tabs
findViewById(R.id.page_one_tab).setBackgroundResource(R.drawable.menu_button_background_active);
findViewById(R.id.page_two_tab).setBackgroundResource(R.drawable.menu_button_background);
findViewById(R.id.page_three_tab).setBackgroundResource(R.drawable.menu_button_background);
}
// Page One Tab Button Functionality
public void pageOneTab (View v)
{
// Change the fragment to SelectPlayersFragment
changeFragment(Page_One_Fragment.class);
}
Attempted Solutions
I've been searching StackOverflow and the internet at large for quite a while and have not been able to find a solution. A few topics like this one seemed as if they would resolve the problem, but then I ran into an error on the transaction.replace line that I could not find a fix for: "The method replace(int, Fragment) in the type FragmentTransaction is not applicable for the arguments (int, Object)".
Perhaps a more simpler solution would be to pass a Fragment as argument, instead of aClass, and use it to replace the current fragment. Also, you don't need to detach the current fragment, that is what replace() does for you.
Something like this:
public void changeFragment(Fragment fragment) {
FragmentManager manager = getSupportFragmentManager();
FragmentTransaction transaction = manager.beginTransaction();
transaction.replace(R.id.fragment_container, fragment, "tag");
transaction.commit();
//.....
}
And you use it like this:
changeFragment(new Page_One_Fragment());
I think you want this:
private <T extends Fragment> void changeFragment(Class<T> newFragmentClass)
{
...
// Create a new instance of the given class, edit later to gracefully handle errors
T new_fragment = newFragmentClass.newInstance();
...
}
Because your "newFragmentClass' is only a parameter of method, it's not a type so you can not instance it.
Follow my code to fix your problem
private <T extends Fragment> void changeFragment(Class<T> newFragmentClass)
{
// Detect the current fragment
Fragment current_fragment = getSupportFragmentManager().findFragmentById(R.id.content);
// Create a new instance of the given class, edit later to gracefully handle errors
T new_fragment = newFragmentClass.newInstance();
// Switch to the player select fragment
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.detach(current_fragment);
transaction.replace(R.id.fragment_container, new_fragment);
transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
transaction.commit();
// Change the tab background to indicate that the tab is active, reset backgrounds for any other tabs
findViewById(R.id.page_one_tab).setBackgroundResource(R.drawable.menu_button_background_active);
findViewById(R.id.page_two_tab).setBackgroundResource(R.drawable.menu_button_background);
findViewById(R.id.page_three_tab).setBackgroundResource(R.drawable.menu_button_background);
}
You probably want something like:
private Fragment changeFragment(Class<? extends Fragment> newFragmentClass) {
Fragment current_fragment = getSupportFragmentManager().findFragmentById(R.id.fragment_container);
// Create a new instance of the given class, edit later to gracefully handle errors
Fragment new_fragment = null;
try {
new_fragment = newFragmentClass.newInstance();
} catch (InstantiationException e) {
throw new RuntimeException(e); // for some reason this fragment loading has failed so crash
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.detach(current_fragment);
transaction.replace(R.id.fragment_container, new_fragment);
// ...

Categories