I have support.v4 ViewPager, which I fill with fragments, by using FragmentStatePagerAdapter.
The displaying of the fragments in ViewPager works as expected, but the problem is getting the fragment views when calling the getView() function in the FragmentActvitiy class. I can access all the 15 fragments and get arguments, etc.., but when calling getView() function on fragment I get a nullpointer exception, becase the third fragment getView() is null.
MyPageAdapter adapter = (MyPageAdapter) pager.getAdapter();
Fragment fragment;
for (int i=0; i<15; i++) {
fragment = adapter.getItem(i);
View v = fragment.getView();
System.out.println("--- FRAGMENT = " + fragment + " VIEW = " + v);
}
Output:
--- FRAGMENT = MyPrvaPomocFragment{40ede4c8 #0 id=0x7f06000d} VIEW = android.support.v4.app.NoSaveStateFrameLayout{40f11cb0 V.E..... ......I. 0,0-540,850}
--- FRAGMENT = MyPrvaPomocFragment{40edf7c0 #1 id=0x7f06000d} VIEW = android.support.v4.app.NoSaveStateFrameLayout{40f16f80 V.E..... ......ID 540,0-1080,850}
--- FRAGMENT = MyPrvaPomocFragment{40ee0998} VIEW = null
FragmentStatePageAdapter class code:
public class MyPageAdapter extends FragmentStatePagerAdapter {
private List<Fragment> fragments;
public MyPageAdapter(FragmentManager fm, List<Fragment> fragments) {
super(fm);
this.fragments = fragments;
}
#Override
public Fragment getItem(int position) {
return this.fragments.get(position);
}
#Override
public int getCount() {
return this.fragments.size();
}
#Override
public int getItemPosition(Object object){
return PagerAdapter.POSITION_NONE;
}
}
Fragment class code:
public class MyPrvaPomocFragment extends Fragment {
public static final String EXTRA_QUESTION_NUMBER = "EXTRA_QUESTION_NUMBER";
public static final String EXTRA_QUESTION = "EXTRA_QUESTION";
...
public static final MyPrvaPomocFragment newInstance(String questionNumber, String question, ..., int correctAnswer) {
MyPrvaPomocFragment f = new MyPrvaPomocFragment();
Bundle bdl = new Bundle(1);
bdl.putString(EXTRA_QUESTION_NUMBER, questionNumber);
bdl.putString(EXTRA_QUESTION, question);
bdl.putInt(EXTRA_CORRECT_ANSWER, correctAnswer);
...
f.setArguments(bdl);
return f;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
String question = getArguments().getString(EXTRA_QUESTION);
String firstAnswer = getArguments().getString(EXTRA_FIRST_ANSWER);
...
View v = inflater.inflate(R.layout.fragment_prva_pomoc, container, false);
TextView textViewQuestion = (TextView) v.findViewById(R.id.textViewQuestion);
CheckBox checkBox1 = (CheckBox) v.findViewById(R.id.checkBox1);
CheckBox checkBox2 = (CheckBox) v.findViewById(R.id.checkBox2);
...
textViewQuestion.setText(question);
checkBox1.setText(firstAnswer);
checkBox2.setText(secondAnswer);
return v;
}
}
FragmentActivity class code:
public class KvizPrvaPomoc extends FragmentActivity {
ViewPager pager;
MyPageAdapter pageAdapter;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.kviz_prva_pomoc);
pager = (ViewPager)findViewById(R.id.viewpagerquiz);
pageAdapter = new MyPageAdapter(getSupportFragmentManager(), getFragments());
pager.setAdapter(pageAdapter);
}
private List<Fragment> getFragments(){
String question;
String firstAnswer;
String secondAnswer;
...
List<Fragment> fragmentList = new ArrayList<Fragment>();
for (int j=0; j<someList.size(); j++) {
...
fragmentList.add(MyPrvaPomocFragment.newInstance(questionNumber, question, firstAnswer, ...));
}
return fragmentList;
}
}
I have read through numerous SO questions/answers, but I have no idea what could be wrong.
This is the expected behavior; FragmentStatePagerAdapter destroys fragments once you scroll more than one fragment away from them in either direction. This is done to conserve memory.
Fragments' views are removed from memory by view pager automatically. This is done to manage resources on mobiles.
You should not try to access visual elements of the fragments from the activity anyway. Only fragment should use its own visual elements, not its parent activity directly.
If you need to manipulate fragments, you should change the data they hold and when the view is recreated (when the user swipes to the fragment), it should restore the view properly based on the data.
Related
I have a View pager. The user can choose how many differents pages he can have.
The pages are all the same layout but it will just load different data.
Here is my fragment adapter :
public class FragmentAdapter extends FragmentPagerAdapter
{
private final List<Fragment> lstFragment = new ArrayList<>();
private final List<String> lstTitles = new ArrayList<>();
public FragmentAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int i) {
return lstFragment.get(i);
}
#Nullable
#Override
public CharSequence getPageTitle(int position) {
return lstTitles.get(position);
}
#Override
public int getCount() {
return lstTitles.size();
}
public void AddFragment (Fragment fragment , String title)
{
lstFragment.add(fragment);
lstTitles.add(title);
}
}
And here is the code to call the fragment multiple time :
FragAdapter = new FragmentAdapter(getSupportFragmentManager());
viewPager = (ViewPager) findViewById(R.id.main_tabs_pager);
toolbar = (Toolbar) findViewById(R.id.main_page_toolbar);
tabLayout = (TabLayout) findViewById(R.id.main_tabs);
String[] Fragments = {"Frag1", "Frag2", "Frag3", "Frag4"};
for (int i=0; i<Fragments.length; i++)
{
((FragmentAdapter) FragAdapter).AddFragment(new MenuFragment(),Fragments[i]);
}
viewPager.setAdapter(FragAdapter);
tabLayout = (TabLayout) findViewById(R.id.main_tabs);
tabLayout.setupWithViewPager(viewPager);
So it works fine. But the only problem is that I don't know how to know the difference in code between the differents fragments.
Exemple :
The frag1 must load 5 pictures about the sea
The frag2 must load 8 pictures about the sun
How can I tell the fragment what to do? I tried to pass in the constructeur the arguments by exemple
public MenuFragment(int numberofpictures, String picturesthemes)
{
// Required empty public constructor
}
but the constructors must be empty because it is not called again when fragment is destroyed and recreated...
does anyone has an idea? thanks
UPDATE
I don't know if that is the good way but here is the way I did it :
In main activity I created :
for (int i=0; i<Fragments.length; i++)
{
Bundle parameters = new Bundle();
parameters.putInt("myInt", i);
Fragment menuFragment = new MenuFragment();
menuFragment.setArguments(parameters);
((FragmentAdapter) FragAdapter).AddFragment(menuFragment, Fragments[i]);
}
Which give a everyfragment the the int i which is a reference to the title.
Then I simply wrote this function :
public String getName (int i)
{
return Fragments[i];
}
which return the title based on the int that the fragment got thanks to the bundle
Then, In the MenuFragment() I used this :
private void fillinthelist()
{
myInt = getArguments().getInt("myInt");
String test = ((MainActivity) getActivity()).getName(myInt);
ListOfProgrammes.add(new Modele_carte(test));
}
so it gets the int from the bundle and make a like to it thanks to the function in MainActivity
Is it the good way to do it? It seems to work
You can attach a Bundle containing the parameters with setArguments(Bundle) :
Bundle parameters = new Bundle();
parameters.putInt("myInt", <int_value>);
Fragment menuFragment = new MenuFragment();
menuFragment.setArguments(arguments);
((FragmentAdapter) FragAdapter).AddFragment(menuFragment, Fragments[i]);
A common practice is to build and attach the Bundle in a fragment's class static factory method.
The fragment can use getArguments() to retrieve the parameters.
private int myInt;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
myInt = getArguments().getInt("myInt");
}
I have a fragment A, containing a listview. To this listview I add a listheader containing a ViewPager that pages through childfragments.
When the user clicks an item in the list, the listfragment A gets replaced by a detail-view-fragment of that listitem.
I want the user to be able to go back to the list by clicking the back button.
So far everything works, except when the user presses the back button to pop the detail fragment from the stack to get back to the listview fragment A, the app crashes with an
java.lang.IllegalArgumentException: No view found for id 0x7f06002e (com.makamedia.hockeyweb:id/news_header_pager) for fragment NewsHeaderFragment{41f7b6f8 #0 id=0x7f06002e android:switcher:2131099694:0}
My suspicion is, that maybe the nested fragments for the viewpager in the listheader get recreated before the viewpager gets recreated, thus crashing the app, but I am not sure.
Any help is appreciated!
My ViewPagerAdapter for the listheader-viewpager (removed some unrelated code):
public class NewsHeaderAdapter extends FragmentPagerAdapter {
private int mCount;
public final NewsListAdapter mListAdapter;
public NewsHeaderAdapter(FragmentManager fm, int count, long autoSwipeInterval, NewsListAdapter adapter) {
super(fm);
this.mCount = count;
this.mListAdapter = adapter;
}
#Override
public Fragment getItem(int pos) {
return NewsHeaderFragment.getNew(this.mListAdapter.getItem(pos));
}
public void setCount(int newCount){
if(newCount < 1){
this.mCount = 1;
} else if(newCount >= this.mListAdapter.getCount()){
this.mCount = this.mListAdapter.getCount();
} else {
this.mCount = newCount;
}
}
#Override
public int getCount() {
return mCount;
}
#Override
public CharSequence getPageTitle(int position) {
return this.mListAdapter.getItem(position).getTitle();
}
}
My news detail fragment (pretty straight forward):
public class NewsHeaderFragment extends Fragment {
private NewsItem mNewsItem;
private TextView mHeaderNewsBigTitle;
private ImageView mHeaderNewsBigImage;
// Convenience method for creating a new fragment with parameters
public static NewsHeaderFragment getNew(NewsItem item){
NewsHeaderFragment fragment = new NewsHeaderFragment();
Bundle args = new Bundle();
args.putSerializable(Constants.SIG_NEWS_ITEM, item);
fragment.setArguments(args);
return fragment;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.row_big_news, container, false);
Bundle newsHeaderArgs = getArguments();
mNewsItem = (NewsItem)newsHeaderArgs.getSerializable(Constants.SIG_NEWS_ITEM);
setupUI(rootView);
fillUI();
return rootView;
}
private void fillUI() {
mHeaderNewsBigTitle.setText(mNewsItem.getTitle());
Picasso.with(getActivity()).load(mNewsItem.getImageBig2x()).into(mHeaderNewsBigImage);
}
private void setupUI(View rootView) {
mHeaderNewsBigTitle = (TextView) rootView.findViewById(R.id.news_big_title);
mHeaderNewsBigImage = (ImageView) rootView.findViewById(R.id.news_big_img);
}
}
My viewpager is declared in xml in a row-layout and added like so:
private void addHeaderPager(int count) {
if(mNewsListAdapter != null && mNewsListAdapter.getCount()>0) {
if (count >= mNewsListAdapter.getCount()) {
count = mNewsListAdapter.getCount() - 1;
}
LayoutInflater inflater = (LayoutInflater) getActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
mHeader = (RelativeLayout) inflater.inflate(R.layout.row_big_news_pager, null);
mHeaderPager = (ViewPager) mHeader.findViewById(R.id.news_header_pager);
mHeaderPagerAdapter = new NewsHeaderAdapter(getChildFragmentManager(), count, 6000, mNewsListAdapter);
mHeaderPager.setOffscreenPageLimit(count);
mHeaderPager.setAdapter(mHeaderPagerAdapter);
// Bind the title indicator to the adapter
CirclePageIndicator circleIndicator = (CirclePageIndicator) mHeader.findViewById(R.id.news_header_pager_indicator);
circleIndicator.setViewPager(mHeaderPager);
mNewsListView.addHeaderView(mHeader);
}
}
Are you sure tha you use the right FragmentManager in addHeaderPager()?
I normally use getFragmentManager() and if there is a parent fragment I have to use getParentFragment().getFragmentManager() - if I don't I get the same error ("No view found for id") when trying to replace the current visible fragment.
I have an Activity with a ViewPager containing multiple fragments. how can i now access a TextView in one of that fragments to change its text from the main activity? I tried multiple ways and they all ended in a NullPointerException.
Activity:
public class SummonerOverview extends SherlockFragmentActivity implements TabListener, OnPageChangeListener {
private ViewPager mPager;
private PagerAdapter mAdapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.summoner_overview);
initialize();
}
private void initialize() {
// initialize Pager
mPager = (ViewPager) findViewById(R.id.viewpager);
mAdapter = new PagerAdapter(getSupportFragmentManager());
mPager.setAdapter(mAdapter);
mPager.setCurrentItem(1);
mPager.setOnPageChangeListener(this);
}
}
PagerAdapter:
public class PagerAdapter extends FragmentPagerAdapter {
public PagerAdapter(FragmentManager fm) {
super(fm);
frags = new Fragment[3];
frags[0] = new StatisticsFragment(0);
frags[1] = new RatingsFragment(1);
frags[2] = new HistoryFragment(2);
}
private final int NUM_PAGES = 3;
Fragment[] frags;
#Override
public Fragment getItem(int arg0) {
if (arg0 == 0)
return frags[0];
else if (arg0 == 1)
return frags[1];
else
return frags[2];
}
#Override
public int getCount() {
return NUM_PAGES;
}
}
The Fragment:
public class StatisticsFragment extends SherlockFragment {
public StatisticsFragment(int fragNr) {
this.fragNr = fragNr;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_overview_statistics, container, false);
return v;
}
}
The Textview in the StatisticsFragment is labeled with an id in the fragment_overview_statistics.xml, but when i try
TextView tv = (TextView) findViewById(R.id.id_of_the_textview)
tv.setText("text");
from within the onCreate() Method of the Activity after the initialize() Method, i get an Exception.
Okay, after another hour of googling, i think i solves my own question (although i'm sure there is a better method to solve that)
I now hold all the Data that is displayed by the fragments in the MainActivity and make the Fragmets consume this Data in their onCreateView() Method by using public Methods of the Activity.
In the PagerAdapter, i overwrote getItemPosition() to:
public int getItemPosition(Object object) {
return POSITION_NONE;
}
Now, everytime i update some of the Data, i also call mAdapter.notifyDataSetChanged() which leads into a recreation of all fragments.
It works, although it looks like a poor hack for me. Im sure there must be a better solution because now i have to recreate all fragments for changing one TextView of one Fragment, what is surely not the way it is intended to be done.
I have an app using SlidingMenu, Action Bar Sherlock and now I am trying to rewrite my previous code to using the nested fragments introduced in support lib with the release of Android 4.2.
The problem is that when I swipe through the fragments, they are visible for a split second and then they disappear (black screen). Sometimes they stay on screen, and sometimes the screen is entirely black and the fragment doesn't appear at all. I suspect I have a lack of understanding of the fragment lifecycle, but I really can't see the error here.
Swiping through them gets me different results. On initial load the first fragment is black. If I swipe to nr 4 and back to nr 1, then nr 1 fragment is OK. If I swipe fast to nr 2 and then to nr 1, the nr 1 is black again. If I swipe slowly from nr 2 to nr 1, then nr 1 stays OK :-/
Here is my code:
public class LeagueInfoFragment extends SherlockFragment {
...
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
if (container != null) {
container.removeAllViews();
}
setRetainInstance(true);
setHasOptionsMenu(true);
mExampleId = getArguments().getInt("mExampleId");
View view = inflater.inflate(R.layout.viewpager_tournament, container,
false);
ViewPager awesomePager = (ViewPager) view.findViewById(R.id.pager);
awesomePager.setAdapter(new ExamplePagerAdapter(this, getChildFragmentManager(), createFragments()));
TabPageIndicator titleIndicator = (TabPageIndicator) view
.findViewById(R.id.titles);
titleIndicator.setViewPager(awesomePager);
return view;
}
private List<Fragment> createFragments() {
List<Fragment> list = new ArrayList<Fragment>();
list.add(LeagueTableFragment.newInstance(0));
list.add(FixturesFragment.newInstance(1));
list.add(NewsFragment.newInstance(2));
list.add(TopscorerFragment.newInstance(3));
return list;
}
private class ExamplePagerAdapter extends FragmentPagerAdapter {
String[] pages;
LeagueInfoFragment _activity;
List<Fragment> fragments;
private ExamplePagerAdapter(LeagueInfoFragment a,
FragmentManager fragManager, List<Fragment> frags) {
super(fragManager);
this.fragments = frags;
_activity = a;
pages = new String[] { _activity.getString(R.string.table),
_activity.getString(R.string.fixtures),
_activity.getString(R.string.news),
_activity.getString(R.string.top_scorers) };
}
#Override
public int getCount() {
return fragments.size();
}
#Override
public Fragment getItem(int arg0) {
return fragments.get(arg0);
}
public String getPageTitle(int position) {
return pages[position];
}
}
And here is one of my fragments (simplified to reduce code posted here):
public class NewsFragment extends SherlockFragment implements IAsynchData
{
public static NewsFragment newInstance(int notUsed) {
NewsFragment f = new NewsFragment();
return f;
}
#Override
public View onCreateView(android.view.LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
if (container != null) {
container.removeAllViews();
}
View view = inflater.inflate(R.layout.fragment_news, container,
false);
//Code left out to load the URL in the webview
updateRssFeed("http://www.sportspage.com/myteam");
return view;
}
}
Sorry, but why are you doing this while onCreateView in Fragment:
if (container != null) {
container.removeAllViews();
}
As I know, in this moment, container is your ViewPager and removeAllViews() - remove all created View of your Fragments in ViewPager.
I'm new to Android developing and I would really appreciate some help here.
I'm using a fragment that contains a TextView and I'm using 5 instances of the same MyFragment class.
In the activity, i got a button and a ViewPager, and I need the button to update all the fragment instances content, whenever its clicked.
Here's the Activity
public class MainActivity extends FragmentActivity {
final static String[] CONTENT = {"a", "b"};
ViewPager pager;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
List<MyFragment> fragments = new Vector<MyFragment>();
for(int i = 0; i < 5; i++){
MyFragment fragment = new MyFragment(CONTENT);
fragments.add(fragment);
}
PagerAdapter adapter = new PagerAdapter(this.getSupportFragmentManager(), fragments);
pager = (ViewPager) findViewById(R.id.viewpager);
pager.setAdapter(adapter);
Button button = (Button) findViewById(R.id.button);
button.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
//method that isn't working
PagerAdapter adapter = (PagerAdapter)pager.getAdapter();
for(int i = 0; i < 5; i++){
MyFragment fragment = (MyFragment) adapter.getItem(i);
fragment.textView.setText(fragment.content[1]);
}
}
});
}
}
The Fragment
public class MyFragment extends Fragment{
String[] content;
TextView textView;
public MyFragment(String[] content) {
this.content = content;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_content, container, false);
textView = (TextView) view.findViewById(R.id.textView1);
textView.setText(content[0]);
return view;
}
}
And the FragmentPagerAdapter
public class PagerAdapter extends FragmentPagerAdapter{
List<MyFragment> fragments;
public PagerAdapter(FragmentManager fm, List<MyFragment> fragments) {
super(fm);
this.fragments = fragments;
}
#Override
public Fragment getItem(int arg0) {
return fragments.get(arg0);
}
#Override
public int getCount() {
return fragments.size();
}
}
The OnClick method gives me a NullPointerException whenever i try to access a fragment from the adapter which is less than adapter.getCurrentItem() - 1, or more than adapter.getCurrentItem() + 1.
Any idea on how to update all the fragments at the same time?
Thanks in advance.
The easiest way to update those fragments is to use your code and set the number of fragments that the ViewPager holds in memory to the number of total fragments - 1(so all fragments are valid no matter at what page you are). In your case:
pager.setOffscreenPageLimit(4); // you have 5 elements
You can still use the method from my comment with the method onPageScrollStateChanged(so the update will start the moment the user starts swiping) to see when the user is starting to swipe the pager and update the fragments to the left and right of the currently visible fragment, but this will be a bit difficult to get right so I recommend to go with the first option.
Some points regarding your code containing fragments:
If you nest the fragment class make it static so you don't tie it to the activity object.
Don't create a constructor for a Fragment besides the default one. If the framework needs to recreate the fragment it will call the default constructor and if it is not available it will throw an exception. For example, try to change the orientation of the phone/emulator and see what happens(this is one of the cases when Android will recreate the fragments). Last, use a custom name for the ViewPager's adapter, you use PagerAdapter which is the name of the super class of FragmentViewPager and it's very confusing for someone reading your code.
If you need to pass data to the Fragment you could use a creation method like the one below:
public static MyFragment newInstance(String text) {
MyFragment f = new MyFragment();
Bundle b = new Bundle();
b.putString("content", text);
f.setArguments(b);
return f;
}
The text will be available in MyFragment by using getArguments().getString("content");