Android: Access a fragment instance from MainActivity - java

I've started coding a small app using Android Studio's pre-defined tabbed Layout with Fragments (SectionsPagerAdapter, ViewPager).
I've got a task running in the MainActivity.class main/Ui thread which at one point shows a dialog with the onClick method
#Override
public void onClick(DialogInterface dialog, int which) {
category = eventsToDisplay.get(which);
averageFragment.category = category;
dialog.dismiss();
}
But I can't get the averageFragment.category = category; assignment to work.
In the MainActivity's onCreate method I call averageFragment = (AverageFragment) getSupportFragmentManager().findFragmentByTag(AverageFragment.tag); but this gives me a NullPointerException.
I have already tried the following solutions (most of which are from this website):
getSupportFragmentManager().findFragmentById(R.id.fragment_average)
getSupportFragmentManager().findFragmentByTag(AverageFragment.tag) <-- basically a static variable created upon instantiating the fragment.
mSectionsPageAdapter.getItem(1)
all of which give me either a NPE or IllegalStateException.FragmentNotAttachedToView.
Other relevant code:
public class SectionsPagerAdapter extends FragmentPagerAdapter {
public SectionsPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
// getItem is called to instantiate the fragment for the given page.
switch (position) {
case 0:
return InspectionFragment.newInstance(position + 1);
case 1:
return AverageFragment.newInstance(position + 1);
case 2:
return RegulationsFragment.newInstance(position + 1);
}
return null;
}
#Override
public int getCount() {
// Show 3 total pages.
return 3;
}
#Override
public CharSequence getPageTitle(int position) {
Locale l = Locale.getDefault();
switch (position) {
case 0:
return getString(R.string.title_section1).toUpperCase(l);
case 1:
return getString(R.string.title_section2).toUpperCase(l);
case 2:
return getString(R.string.title_section3).toUpperCase(l);
}
return null;
}
}
Any ideas on how to access the fragment from the main activity?

Check the location where you assign the instance of your adapter to the pager. The exception your getting means that the fragment hasn't loaded to the view yet, which is likely given that you're calling your assignment:
averageFragment = (AverageFragment) getSupportFragmentManager().findFragmentByTag(AverageFragment.tag);
from the onCreate() method. Try moving this assignment to the onResume(), which should ensure that your fragment has been loaded into the view and is accessible through the supportFragmentManager. Also make sure this assignment is occurring after you set the pager's adapter within the lifecycle of your activity.

Related

ViewPager not updating fragments

I'm trying to replace fragments in ViewPager, but I'm facing a problem I've been unable to fix for several days. The relevant code and specific problem, as I understand it, are described below:
public class ViewPageAdapter extends FragmentStatePagerAdapter {
int mNumOfTabs;
FragmentManager mFragmentManager;
Fragment0 currentFragment0;
Fragment1 currentFragment1;
Fragment2 currentFragment2;
boolean getItemNeverCalled = true;
public ViewPageAdapter(FragmentManager fm, int numOfTabs){
super(fm);
mFragmentManager = fm;
this.mNumOfTabs = numOfTabs;
}
#Override
public Fragment getItem(int position){
switch (position){
case 0:
if(currentFragment0 == null){
Fragment0 tab0 = new Fragment0();
currentFragment0 = tab0;
return currentFragment0;
}
else {
mFragmentManager.beginTransaction().remove(currentFragment0).commit();
int value = selectedPlant.getMoistureFrag().getStat().getOptimalLevel();
currentFragment0 = Fragment0.newInstance(key0, value);
notifyDataSetChanged(); // calls getItem(0).
return currentFragment0;
}
case 1:
if(currentFragment1 == null){
LightFragment tab1 = new Fragment1();
currentFragment1 = tab1;
return currentFragment1;
}
else {
mFragmentManager.beginTransaction().remove(currentFragment1).commit();
int value = selectedPlant.getLightFrag().getStat().getOptimalLevel();
currentFragment1 = currentFragment1.newInstance(key1, value);
notifyDataSetChanged();
return currentFragment1;
}
case 2:
if(currentFragment2 == null){
Fragment2 tab2 = new Fragment2();
currentFragment2 = tab2;
return currentFragment2;
}
else {
mFragmentManager.beginTransaction().remove(currentFragment2).commit();
int value = selectedPlant.getTempFrag().getStat().getOptimalLevel();
currentFragment2 = Fragment2.newInstance(key2, value);
notifyDataSetChanged();
return currentFragment2;
}
default:
return null;
}
}
#Override
public int getCount(){
return mNumOfTabs;
}
#Override
public int getItemPosition(Object object){
return POSITION_NONE;
}
I've overriden the getItemPosition(Object object) method to always return POSITION_NONE, and called notifyDataSetChanged() when appropriate (I think). What ends up happening is that notifyDataSetChanged() calls getItem(0), which calls `notifyDataSethanged()... and so on. This causes a TransactionTooLargeException and crashes the app.
Just to give some background to the if/else statements in each case: the if is meant to load a blank Moisture/Light/etc Fragment onto the screen. This is intended to happen on start-up. The else statement is executed when a user presses on a item in the navigation drawer, which has some data. This data is then extracted and set as arguments for the fragments that are meant to replace the initial blank fragment.
I genuinely appreciate any help. This problem is driving me crazy.
Why in the world are you recreating fragments, when you can just update the old ones?
Also, when you are calling notifyDataSetChanged during getItem then you are forcing a new call to getItem which will force a new call...so you are actually creating a circular call!
Since you are always keeping the same fragment class in each position, and you are holding on to the fragment, then you should not replace fragment. Just change the fragment you are holding to show the new values. The code you are using is only needed if you want to change different fragment classes for position.

How to update All Pages in Page Viewer?

I have a Page Viewer with three pages with ArrayLists in them, represented as "Tomorrow" "Today" and "Yesterday". I also have a Drawer that allows to change settings of the Lists.
When the Drawer closes, I want the ListsViews (or the entire page fragment) to update to show three new ArrayLists that were created after the new settings were applied.
So far, I managed to be able to update "Yesterday" and "Tomorrow" (When sliding to yesterday, tomorrow updates and vice versa), I think that is because "Today" never gets destroyed.
Either way, I would really like to see all three update as soon as the Drawer closes.
Here is the code for my Adapter:`
private class MyPagerAdapter extends FragmentPagerAdapter {
SparseArray<Fragment> registeredFragments = new SparseArray<Fragment>();
ListFragmentOfTomorrow torrowFragment =new ListFragmentOfTomorrow();
ListFragmentOfToday todayFragment = new ListFragmentOfToday();
ListFragmentOfYesterday yestFragment = new ListFragmentOfYesterday();
public MyPagerAdapter(android.support.v4.app.FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int pos)
{
switch (pos) {
case 0:
torrowFragment.newInstance(tomorrowArrayList, MainActivity.this);
registeredFragments.put(0, torrowFragment);
return torrowFragment;
case 1:
todayFragment.newInstance(todayArrayList, MainActivity.this);
registeredFragments.put(1, todayFragment);
return todayFragment;
case 2:
yestFragment.newInstance(yesterdayArrayList, MainActivity.this);
registeredFragments.put(2, yestFragment);
return yestFragment;
default:
todayFragment.newInstance(todayArrayList, MainActivity.this);
registeredFragments.put(3, todayFragment);
return todayFragment;
}
}
#Override
public int getCount() {
return 3;
}
public Fragment getRegisteredFragment(int position) {
return registeredFragments.get(position);
}
}`
This is the code for one of the page Fragment (all three are basically the same):
public class ListFragmentOfToday extends Fragment
{
static ExpandListAdapter ExpAdapter;
static ExpandableListView expndList;
static Context context;
static ArrayList<Game> todayArrayList;
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
View v = inflater.inflate(R.layout.fragment_list_of_today, container, false);
expndList = new ExpandableListView(context);
expndList = (ExpandableListView)v.findViewById(R.id.FragmentedExpandableListView);
ExpAdapter = new ExpandListAdapter(context,todayArrayList);
expndList.setAdapter(ExpAdapter);
return v;
}
public static ListFragmentOfToday newInstance(ArrayList<Game> todayArrayListIn, Context contextIn)
{
ListFragmentOfToday todayFragment = new ListFragmentOfToday();
context = contextIn;
todayArrayList = todayArrayListIn;
return todayFragment;
}
public void RefreshList(ArrayList<Game> todayArrayListIn)
{
todayArrayList=todayArrayListIn;
ExpAdapter.notifyDataSetChanged();
}
}
This is the code for when the Drawer closes:
#Override
public void onDrawerClosed(View drawerView)
{
//Here, the new ArrayLists are created(...)
ListFragmentOfTomorrow tomorrowFragmentToUpdate = (ListFragmentOfTomorrow)pagerAdapter.getRegisteredFragment(0);
tomorrowFragmentToUpdate.RefreshList(updatedTomorrowArrayList);
ListFragmentOfToday todayFragmentToUpdate = (ListFragmentOfToday)pagerAdapter.getRegisteredFragment(1);
todayFragmentToUpdate.RefreshList(updatedTodayArrayList);
ListFragmentOfYesterday yesterdayFragmentToUpdate = (ListFragmentOfYesterday)pagerAdapter.getRegisteredFragment(2);
yesterdayFragmentToUpdate.RefreshList(updatedYesterdayArrayList);
}
Question: How can I get all three pages to show the new updated arraylists as soon as the drawer closes?
As a new developer and a new StackOverflow user, I would also like to get any feedback on my code writing and my question format. Thank you.
Thank you Thomas! That worked. I tried notifyDataSetChanged() and getItemPosition(Object object), but never together.
dhke - I saw the instantiating fragments when I was looking for an answer, but understood the difference to newInstance()...

Where is my static context?

I'm brand new to android programming and I'm trying to use a gridviewpager with fragments. My code is:
public class Main extends Activity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.grid);
final Resources res = getResources();
GridViewPager pager = (GridViewPager) findViewById(R.id.gridpager);
pager.setOnApplyWindowInsetsListener(new OnApplyWindowInsetsListener() {
#Override
public WindowInsets onApplyWindowInsets(View v, WindowInsets insets) {
// Adjust page margins:
// A little extra horizontal spacing between pages looks a bit
// less crowded on a round display.
final boolean round = insets.isRound();
int rowMargin = res.getDimensionPixelOffset(R.dimen.page_row_margin);
int colMargin = res.getDimensionPixelOffset(round ?
R.dimen.page_column_margin_round : R.dimen.page_column_margin);
pager.setPageMargins(rowMargin, colMargin);
// GridViewPager relies on insets to properly handle
// layout for round displays. They must be explicitly
// applied since this listener has taken them over.
pager.onApplyWindowInsets(insets);
return insets;
}
});
// MyPagerAdapter adapter=new MyPagerAdapter();
pager.setAdapter(new MyPagerAdapter(Activity.getFragmentManager()));
}
public class MyPagerAdapter extends FragmentPagerAdapter {
public MyPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int pos) {
switch(pos) {
case 0: return FirstFragment.newInstance("FirstFragment, Instance 1");
case 1: return SecondFragment.newInstance("SecondFragment, Instance 1");
case 2: return SecondFragment.newInstance("SecondFragment, Instance 2");
default: return SecondFragment.newInstance("SecondFragment, Default");
}
}
#Override
public int getCount() {
return 3;
}
}
}
I've collapsed some to be concise. The error is in this line:
pager.setAdapter(new MyPagerAdapter(Activity.getFragmentManager()));
This returns the error:
Non-static method 'getFragmentManager()' cannot be referenced from a static context
But I don't know what is static in my code. I've tried assigning everything I can think of to variables but still can't get this. Thanks for your help.
Activity is the name of a class. By stating Activity.getFragmentManager(), you are attempting to call a method on the class itself rather than a specific instance of the class.
In your case, you don't need the Activity. section at all - just call getFragmentManager() itself:
pager.setAdapter(new MyPagerAdapter(getFragmentManager()));

Passing data between two Fragments in a VIewPager (Android) (NullPointerException)

So basically I have 2 Fragments - FragmentConverter and FragmentFavourites, and I have one MainActivity. I'm trying to pass 4 arrays from the first fragment to the second one using an Interface called Communicator. The specific snippets are show below:
public interface Communicator {
public void respond(String[] names, String[] codes, String[] symbols, int[] images);
}
This is a method inside FragmentFavourites:
#Override
public boolean onOptionsItemSelected(MenuItem item) {
// TODO Auto-generated method stub
String[] checkedNames = new String[counter];
String[] checkedCodes = new String[counter];
String[] checkedSymbols = new String[counter];
int[] checkedImages = new int[counter];
comm = (Communicator) getActivity();
int index = 0;
if (item.getItemId() == R.id.action_save){
for (int i=0;i<checked.size();i++){
if (checked.get(i) == true){
checkedNames[index] = names[i];
checkedCodes[index] = codes[i];
checkedSymbols[index] = symbols[i];
checkedImages[index] = images[i];
index++;
}
}
comm.respond(checkedNames, checkedCodes, checkedSymbols, checkedImages);
}
return super.onOptionsItemSelected(item);
}
This is the implemented interface method inside MainActivity:
#Override
public void respond(String[] names, String[] codes, String[] symbols,
int[] images) {
// TODO Auto-generated method stub
FragmentConverter frag = (FragmentConverter) fragmentPagerAdapter.getItem(1);
frag.changeData(names, codes, symbols, images);
}
And this is a method that collects the data in FragmentConverter:
public void changeData(String[] names, String[] codes, String[] symbols, int[] images){
this.names = names;
this.codes = codes;
this.symbols = symbols;
this.images = images;
Log.d("TEST", symbols.length + names.length + codes.length + images.length + "");
tvOneRate.setText(names[1]);
}
Now the problem is that whenever I try to change a ui component inside FragmentConverter, I get a NullPointerException, though the Log.d statement returns the correct results.
EDIT1: getItem() method of FragmentPagerAdapter:
#Override
public Fragment getItem(int i) {
// TODO Auto-generated method stub
Fragment frag = null;
if (i == 0){
frag = new FragmentFavourites();
}
if (i == 1){
frag = new FragmentConverter();
}
return frag;
}
EDITED:
When you call fragmentPagerAdapter.getItem(1) you are getting a new instance of the fragment so you are referring to a different object. this is why the view is null and you get the NullPointerException. If you need an adapter for only 2 fragments, you can try with something like that:
public class YourPagerAdapter extends android.support.v4.app.FragmentPagerAdapter {
private FragmentFavourites mFragFavourites;
private FragmentConverter mFragConverter;
public YourPagerAdapter() {
// ... your code above
this.mFragFavourites = new FragmentFavourites();
this.mFragConverter = new FragmentConverter();
}
#Override
public Fragment getItem(int position) {
switch (position) {
case 0:
return mFragFavourites;
case 1:
return mFragConverter;
default:
return null;
}
}
}
As above carlo.marinangeli has suggested when you call fragmentPagerAdapter.getItem(1) you are getting a new instance of the fragment so you are referring to a different object
So to get same object from you adapter you need to store your object. you can try following method in your adapter -
public Fragment getFragmentAtPosition(int position) {
return registeredFragments.get(position);
}
where registeredFragments is -
private SparseArray<Fragment> registeredFragments = new SparseArray<Fragment>();
and fill this sparseArray in getItem method like below -
#Override
public Fragment getItem(int position) {
switch (position) {
case 0:
fragment = FragmentPost.newInstance(position);
registeredFragments.put(position, fragment);
return fragment;
}
return null;
}
By using fragmentPagerAdapter.getItem(pos) method I was referring to a new object every time the respond() method was called. I fixed it by using findFragmentByTag() method instead:
#Override
public void respond(String[] names, String[] codes, String[] symbols,
int[] images) {
FragmentManager manager = getSupportFragmentManager();
FragmentConverter frag = (FragmentConverter) manager.findFragmentByTag("android:switcher:" + pager.getId() + ":" + 1);
frag.changeData(names, codes, symbols, images);
}
you can get that error because you are assuming that you have got the FragmentConverter and the views associated to it.
Without a logcat it becomes a little bit difficult to help you but basically what I would like to do in a situation like this is to pass everything through the activity without letting know the existence of the other fragment to the fragments.
F1 modifies a state object into the activity
F2 has to register as a
listener to the activity (be aware that the fragment can be attached
and detached in the view pager)
The Activity as soon it receives an updated, looks for all the registered listeners and if there is someone it delivers the updated

Android: Passing Objects Between Fragments

Before i start, i have look through question such as:
Passing data between fragments: screen overlap
How to pass values between Fragments
as well as Android docs:
http://developer.android.com/training/basics/fragments/communicating.html
as well as this article:
http://manishkpr.webheavens.com/android-passing-data-between-fragments/
Though all the cases mentioned above similar to what i have, it is not entirely identical. I followed a good tutorial here (Some portion of my code is based on this article):
http://www.androidhive.info/2013/10/android-tab-layout-with-swipeable-views-1/
I have the following files:
RegisterActivity.java
NonSwipeableViewPager.java
ScreenSliderAdapter.java
RegisterOneFragment.java
RegisterTwoFragment.java
And the following layouts:
activity_register.xml
fragment_register_one.xml
fragment_register_two.xml
What i am trying to achieve is passing an Serializable object from RegisterFragmentOne to RegisterFragmentTwo.
So far this is what i have done (some codes are omitted):
RegisterActivity.java
public class RegisterActivity extends FragmentActivity
implements RegisterOneFragment.OnEmailRegisteredListener{
public static NonSwipeableViewPager viewPager;
private ScreenSliderAdapter mAdapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_register);
// Initilization
mAdapter = new ScreenSliderAdapter(getSupportFragmentManager());
viewPager = (NonSwipeableViewPager) findViewById(R.id.pager);
viewPager.setAdapter(mAdapter);
}
public void onEmailRegistered(int position, Registration regData){
Bundle args = new Bundle();
args.putSerializable("regData", regData);
viewPager.setCurrentItem(position, true);
}
}
ScreenSliderAdapter.java
public class ScreenSliderAdapter extends FragmentPagerAdapter{
public ScreenSliderAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int index) {
switch (index) {
case 0:
return new RegisterOneFragment();
case 1:
return new RegisterTwoFragment();
case 2:
return new RegisterThreeFragment();
}
return null;
}
#Override
public int getCount() {
return 3;
}
}
NonSwipeableViewPager.java (extending ViewPager class, and overrides the following)
#Override
public boolean onInterceptTouchEvent(MotionEvent arg0) {
// Never allow swiping to switch between pages
return false;
}
#Override
public boolean onTouchEvent(MotionEvent event) {
// Never allow swiping to switch between pages
return false;
}
RegisterOneFragment.java
public class RegisterOneFragment extends Fragment {
OnEmailRegisteredListener mCallBack;
public interface OnEmailRegisteredListener {
/** Called by RegisterOneFragment when an email is registered */
public void onEmailRegistered(int position, Registration regData);
}
public void onAttach(Activity activity){
super.onAttach(activity);
// This makes sure that the container activity has implemented
// the callback interface. If not, it throws an exception.
try {
mCallBack = (OnEmailRegisteredListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString()
+ " must implement OnEmailRegisteredListener");
}
}
... And some to execute some HTTP request via separate thread...
}
What i am trying to accomplish is that when ever a user pressed a button on RegisterOneFragment, a data will be sent to a server (and returns some validation over JSON). If the returned data is valid, the the application should go to the next fragment which is RegistrationTwoFragment.
I am having some confusion as how to pass objects between fragments, since my Fragments is created using an Adapter. And that Adapter is then attached to my Activity.
Can anyone help me with this? Thx
Edit 1:
I tried to make a shortcut (unfortunately does not work) like so:
In RegisterActivity i created:
public Registration regData;
and in RegisterOneFragment:
/* PLACED ON POST EXECUTE */
((RegisterActivity)getActivity()).regData = regData;
Finally called it in RegisterTwoFragment
Registration regData;
regData = ((RegisterActivity) getActivity()).regData;
It throws a nullPointerExceptions
Edit 2
Just to be clear, RegisterActivty contains multiple fragments. And the only way user can navigate between fragment is by clicking a button. The Activity has no Tab bar.
It's easy to share objects via implementing Serializable to your custom Object. I wrote a tutorial about this here.
From Fragment One:
android.support.v4.app.FragmentTransaction ft =
getActivity().getSupportFragmentManager().beginTransaction();
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
OfficeCategoryFragment frag = new OfficeCategoryFragment();
Bundle bundles = new Bundle();
Division aDivision = divisionList.get(position);
// ensure your object has not null
if (aDivision != null) {
bundles.putSerializable("aDivision", aDivision);
Log.e("aDivision", "is valid");
} else {
Log.e("aDivision", "is null");
}
frag.setArguments(bundles);
ft.replace(android.R.id.content, frag);
ft.addToBackStack(null);
ft.commit();
In Fragment two:
Bundle bundle = getArguments();
Division division= (Division) bundle.getSerializable("aDivision");
Log.e("division TEST", "" + division.getName());
I would normally have setters or methods similar to this in the containing activity.
So if I understand correctly, you want the user to access RegistrationOneFragment, then when completed, use this data, validate it, and if valid, pass it along to RegistrationTwoFragment and move the user to this Fragment.
Could you simply call validateJson(regData) in your onEmailRegistered method to handle the validation in your activity, if it succeeds, commit a transaction to RegistrationTwoFragment.
Then all you need are getters and setters in your activity or Fragment to say getRegistrationOneData() in the activity or setData(Registration args) in the fragment as your examples show above.
I don't know of any way to pass the args directly into the Fragment.
I found a solution to my question, which i am sure not the correct way to do that...
So in RegisterActivity.java i add + modified the following lines (thx to #sturrockad):
public Registration getRegistrationData(){
return this.regData;
}
public void onEmailRegistered(int position, Registration regData){
this.regData = regData;
viewPager.setCurrentItem(position, true);
}
Then in RegisterTwoFragments.java (or in the Fragment to which i want to receive the Object):
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_register_two, container, false);
regData = ((RegisterActivity) getActivity()).getRegistrationData();
...
I used to set object with Pacelable or Serializable to transfer, but whenever I add other variables to object(model), I have to register it all. It's so inconvenient.
It's super easy to transfer object between activities or fragments.
Android DataCache
put your data object to KimchiDataCache instance in your activity or fragment.
User userItem = new User(1, "KimKevin"); // Sample Model
KimchiDataCache.getInstance().put(userItem);
// add your activity or fragment
Get your data object in your activity of fragment that you added.
public class MainFragment extends Fragment{
private User userItem;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
userItem = KimchiDataCache.getInstance().get(User.class);
}

Categories