I keep running into a recurring issue in many of my apps and have been using all kinds of work arounds to "solve" it, but this time I've had it and I want to figure out a real solution.
I am trying to build a tabbed layout with two tabs where each tab shows some data which should be obtained from the internet. Once the data is obtained it is cached on the device so it can be restored instantly the next time the app is opened (and will then be refreshed in the background).
To this effect I am trying to load the cached data and display it in a RecyclerView in the first tab, and I want to do this on activity create. Before I do this I obviously set up all the tab layout stuff so that the tabs should be properly loaded. The problem is that they are not, it seems the Fragments that make up the tab pages don't have their views yet, hence I cannot access the RecyclerView on them.
Here is my Activity code:
public class MainActivity extends NetworkBusActivity
{
// Views
ViewPager tabPager;
TabLayout tabLayout;
// Tab pager adapter
private ViewPagerAdapter adapter;
// Fragment one and two
private MenuFragment menuFragment;
private OrderFragment orderFragment;
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Create the views
tabPager = (ViewPager) findViewById(R.id.tabPager);
tabLayout = (TabLayout) findViewById(R.id.tabLayout);
// Setup the tab layouts
this.setupTabs();
// Show cached data
this.setCachedItems();
// Start loading new data in background
this.startLoading();
}
private void setupTabs()
{
// Create Fragments
menuFragment = new MenuFragment();
orderFragment = new OrderFragment();
// Setup adapter
adapter = new ViewPagerAdapter(getSupportFragmentManager());
adapter.addFragment(menuFragment, getString(R.string.title_menu));
adapter.addFragment(orderFragment, getString(R.string.title_orders));
tabPager.setAdapter(adapter);
// Setup the tab layout
tabLayout.setupWithViewPager(tabPager);
tabLayout.setTabGravity(TabLayout.GRAVITY_FILL);
tabLayout.setOnTabSelectedListener(new TabLayout.OnTabSelectedListener()
{
// not shown
});
}
private void setCachedItems()
{
// Show cached data
ArrayList<Item> items = Cache.menu.getItems();
menuFragment.setItems(items);
}
private void startLoading()
{
// Start loading in background (not shown)
}
}
It should be straightforward: create the views, create the fragments, and setup the tab layout, then load the cached data.
The MenuFragment extends a base class ItemListFragment which defines the setItems method:
public class MenuFragment extends ItemListFragment
{
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
View view = inflater.inflate(R.layout.tab_menu, container, false);
return view;
}
#Override
protected ItemListAdapter getAdapter(ArrayList<Item> items, boolean categorize)
{
return new MenuListAdapter(this, R.layout.row_item, items, categorize);
}
}
public abstract class ItemListFragment extends Fragment
{
private RecyclerView recycler;
#Override
public void onViewCreated(View view, Bundle savedInstanceState)
{
super.onViewCreated(view, savedInstanceState);
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(getActivity());
recycler = (RecyclerView) view;
recycler.setLayoutManager(linearLayoutManager);
recycler.addItemDecoration(new DividerItemDecoration(getActivity(), DividerItemDecoration.VERTICAL_LIST));
}
public void setItems(ArrayList<Item> items)
{
recycler.setAdapter(getAdapter(items, true));
}
protected abstract ItemListAdapter getAdapter(ArrayList<Item> items, boolean categorize);
}
Again straightforward: create the view in onCreateView, then obtain the RecyclerView in onViewCreated, and finally set the items with an adapter.
The problem is simple: the method setCachedItems in MainActivity is called before the onCreateView or onViewCreated methods are called in the Fragments. Hence, the RecyclerView is null and I can't set its adapter. Even though I am creating a new instance of the Fragments and adding them to a functional TabLayout before I call that method.
There seems to be some delay before the views are created, but I need to set the items already when the activity is created.
Where am I going wrong, and how do I fix it?
In onViewCreated of fragment do this
((MainActivity)getActivity).setCachedData().....
Instead of on create of activity
Related
I'm using a TabLayout with ViewPager. I have two tabs in my tab layout which both have a different instances of the same fragment object. Both fragments have the same recycler view layout as well.
Problem: When I load the entire activity after getting the server objects back, I'm trying to populate both tab views with the new object, but I get the following error:
java.lang.IllegalStateException: FragmentManager is already executing
transactions
. So in my main activity:
public class MyMainActivity extends FragmentActivity {
private TabLayout tabLayout;
private ViewPager viewPager;
private MyAdapter adapter;
private MyFragment fragmentA;
private MyFragment fragmentB;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.my_activity);
tabLayout = (TabLayout)findViewById(R.id.tab_layout);
viewPager = (ViewPager)findViewById(R.id.view_pager);
adapter = new MyAdapter(getSupportFragmentManager(), new
ArrayList<>(), new ArrayList<>());
// Instantiate the fragments here
adapter.addFragment(....A, "Title A");
adapter.addFragment(....B, "Title B");
.....
.....
.....
viewPager.setAdapter(adapter);
tabLayout.setupWithViewPager(viewPager);
}
public void refreshPage() {
runOnUiThread(new Runnable() {
#Override
public void run() {
// This line crashes
adapter.notifyDataSetChanged();
}
});
}
}
I tried using getChildFragmentManager but I can't because you need to call that via a fragment object. I cannot show much code as most of it is proprietary and I had to make the code as generic as possible.
When I comment out the line that crashes, my recycler view for the first tab does not refresh unless I scroll. while the second tab does not refresh at all. Did anyone have the same issue before? How did you resolve it?
P.S I'm not using any FragmentManager or transactions so I'm not sure why it gives me that exception.
I have been trying to implement a ViewPager with different fragments.
And the problem is when i run the app, in the ViewPager, out of all the pages, only one page is visible and that page only gets changed when I slide over to the other pages in the ViewPager.
Take a look at my code,(although I checked it many times referring it with online resources).
This is what each of my fragments look like:
public class fragment1 extends Fragment {
/* Variable to store reference to the ACtivity */
Activity mCurrentActivity;
/* Variable storing reference to the ArrayList */
private ArrayList<Word> mDefaultWords;
/**
* THe empty public Constructor
*/
public fragment1(){
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
/** Getting reference to the Activity */
mCurrentActivity = getActivity();
// Populating the ArrayList here
// And later in the onActivityCreated callback I set an adapter on the ArrayList
return inflater.inflate(R.layout.activity_others, container, false);
}
#Override
public void onActivityCreated(Bundle savedStateInstance){
super .onActivityCreated(savedStateInstance);
/**
* Creating {#link ArrayAdapter} to link the {#link String}
* from {#link ArrayList} {#param
*/
MyAdapter adaptItems = new MyAdapter(mCurrentActivity, mDefaultWords);
// Getting the id of the ListView in numberActivity.xml
ListView myList = (ListView) mCurrentActivity.findViewById(R.id.theList);
//Chaning background color
myList.setBackgroundColor(ContextCompat.getColor(getContext(), android.R.color.holo_purple));
// Setting the adapter with the {#link ListView}
myList.setAdapter(adaptItems);
}
}
}
My Activity setting the adapter class extending FragmentPagerAdapter as a private inner class and setting the adapter on the ViewPager.
public class Main2Activity extends AppCompatActivity {
private ViewPager mViewPager;
private FragmentPagerAdapter mFragmentStatePagerAdapter;
private FragmentManager mFragmentManager;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
mFragmentManager = getSupportFragmentManager();
mViewPager = (ViewPager) findViewById(R.id.theViewPager);
mFragmentStatePagerAdapter = new MyFragmentStatePagerAdapter(mFragmentManager);
/* Setting the apdapter on the pager */
mViewPager.setAdapter(mFragmentStatePagerAdapter);
}
public class MyFragmentStatePagerAdapter extends FragmentPagerAdapter {
public MyFragmentStatePagerAdapter(FragmentManager fragmentManager){
super(fragmentManager);
}
#Override
public int getCount(){
return 4;
}
#Override
public Fragment getItem(int position) {
if (position == 0) {
return new fragment1();
} else if (position == 1){
return new fragment2();
} else if (position == 2) {
return new fragment3();
} else {
return new fragment4();
}
}
}
}
And here is the layout with the ViewPager
<android.support.v4.view.ViewPager
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/theViewPager"
android:visibility="visible" />
As I said, when I run the app only one page gets displayed, other pages are present in the ViewPager but they are blank and displays the default background color,
And the one page that is displayed is the one that gets changed when I swipe left or right in the ViewPager.
So what's the issue?
dont downvote the question, its a genuine problem.
So, I worked my way around, let me say how.
What happened is, I was working on a cloned project that had a old gradle version and sdktools version was also not updated and was quite old.
and the min API targetted was API 15
And I was testing my application on API 21.
So, what I did is I used a different layouts for each of my fragments.
That is for each fragment I created its own XML layout.
And that worked perfectly.
Odd problem, so I updated the gradle and sdktools, to avoid such weird problems.
I have two fragments, fragment_one (fragmentOne.class) and fragment_two (fragmentTwo.class). These fragments are being displayed inside ActivityMain.class.
When fragment_one is being displayed I want to set Image A as the background for ActivityMain.class.
When fragment_two is being displayed I want to set Image B as the background for ActivityMain.class
I can swap and set the background of ActivityMain.class when I am not using fragments (i use buttons as a test)....But, when I modify this for fragments I cannot get it to work.
Listed here you will see my ActivityMain.class
public class ActivityMain extends FragmentActivity {
//set variables for use of pageradapter and swipe screen
MyPageAdapter adapter;
ViewPager pager;
Context context = this;
LinearLayout swipeHomeScreen;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.content_activity_nav);
//Implement the adapater for the swipe screen on launch page and circle indicator
adapter = new MyPageAdapter(getSupportFragmentManager());
pager = (ViewPager) findViewById(R.id.pager);
pager.setAdapter(adapter);
swipeHomeScreen = (LinearLayout) findViewById(R.id.swipeHomeScreen);
}
}
Here I have listed the code of one of my fragments where I am trying to share/pass the set the background image of ActivityMain.class...
public class ActivityNavSwipeTwo extends Fragment {
ActivityMain main;
public void onAttach(Activity activity) {
main = (ActivityMain) activity;
};
public static ActivityNavSwipeTwo newInstance() {
ActivityNavSwipeTwo fragment = new ActivityNavSwipeTwo();
return fragment;
}
public ActivityNavSwipeTwo() {
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View root = inflater.inflate(R.layout.fragment_two, null);
main.swipeHomeScreen.setBackgroundResource(R.drawable.Image2);
return root;
}
}
My activity inflates the fragments fine, but the background image does not change.
How could I implement this for target api 23??
Thanks for your support.
Implement viewpager.onpagechangedlistener in your activity. Then add view.setonpageselecterlistener(this) in oncreated method. You can control selected fragment in implemented on pageselected method
Definitely you have any parent layout(Relative/Linear) for your activity. So you have to do following:
Create id of parent layout if not created.
access from fragment and change the background color of that parent layout.
Write this in Fragment's onCreateView() method.
relativeLayout = (RelativeLayout)getActivity().findViewById(R.id.relativelayout);
relativeLayout.setBackground(background);
I've been searching for hours and tried numerous methods but cannot seem to grasp my head around the idea / figure out how to retain/restore data in a ViewPager Fragment when it is destroyed and then recreated.
Here is what I have -
An activity where I setup the ViewPager and PageAdapter
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.view_pager);
//Setup pager and adapter
mPager = (ViewPager) findViewById(R.id.viewpager);
mPagerAdapter = new ScreenSlidePagerAdapter(getSupportFragmentManager());
mPager.setAdapter(mPagerAdapter);
}
My PageAdapter where I setup a fragment with a bundle using .newInstance()
#Override
public Fragment getItem(int position) {
ScreenSlidePageFragment fragment = new ScreenSlidePageFragment();
return fragment.newInstance(position);
}
My Fragment that has a layout that includes a TextView that shows the user a question, a picture, and two True/False buttons. New instance is returned back to the Adapter.
public static ScreenSlidePageFragment newInstance(int position) {
ScreenSlidePageFragment fragment = new ScreenSlidePageFragment();
Bundle args = new Bundle();
args.putInt("page_position", position + 1);
fragment.setArguments(args);
return fragment;
}
//the fragment is newly created for the first time or recreated when exiting the view
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_main, parent, false);
//Handle a question being displayed on each fragment
count = getArguments().getInt("page_position") - 1;
mQuestionText = (TextView)v.findViewById(R.id.questionText);
mQuestionText.setText(bank.get(count).getQuestion());
//change the image depending on correct / incorrect answer
mPhoto = (ImageView)v.findViewById(R.id.imageView);
trueButton = (Button)v.findViewById(R.id.true_button);
falseButton = (Button)v.findViewById(R.id.false_button);
//True Button is pressed
trueButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
if(bank.get(count).getAnswer()) {
mPhoto.setImageResource(R.drawable.right);
clickable = false;
}
else {
mPhoto.setImageResource(R.drawable.wrong);
clickable = false;
}
trueButton.setClickable(clickable);
falseButton.setClickable(clickable);
}
});
What I cannot figure out for the life of me, is how to retain/save that fact that the user has pressed a button and which picture to display when the fragment is restored. I have tried a number of options using onResume(), getArguments(), onSaveInstanceState(), onActivityCreated() etc but none of them seem to work.
I can fix the problem by keeping all my ViewPager pages alive using setOffscreenPageLimit(total pages) but have read this is a bad idea since it takes up a large amount of memory.
Here is an example that I use that can help you where an ArrayList of urls for pictures is saved for later when the view is recreated, you need to override onSavedInstanceState and save your variables in a bundle, then retrieve the values from the bundle when the view is created again, hope this can help
ArrayList<String> picutersUrl = new ArrayList<String>();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if(savedInstanceState!=null)
{
picutersUrl = savedInstanceState.getStringArrayList("My Pictures");
}
#Override
protected void onSaveInstanceState(Bundle bundle)
{
super.onSaveInstanceState(bundle);
bundle.putStringArrayList("My Pictures", picutersUrl );
}
I too have used PagerAdapter to support few static tab widgets. I think you need to add code to ScreenSlidePagerAdapter. Google webpage at PagerAdapter.
Note and read the details on override method instantiateItem. This is where you populate the UI.
Code example for ScreenSlidePagerAdapterfrom (PagerAdapter subclass), using your posted code:
#Override
public Object instantiateItem(ViewGroup container, int position) {
View v = inflater.inflate(R.layout.fragment_main, parent, false);
...
trueButton = (Button)v.findViewById(R.id.true_button);
}
In reality with Views in this framework, you're responsible on saving the state of UI elements. In my ListView, I am caching and saving up to 100 rows of data and refreshes it in an Adapter, for example. For caching, I created custom class containing the UI data.
Need some help with my problem of updating pages while using viewpager. I am using a simple viewpager with FragmentStatePagerAdapter. All I want to do is get access to the current fragment/view so as I can update some textviews in my fragment/view. I searched around in the forum and came to know few things
- One of the ways to handle this is by setting tag in instantiateItem() call back of the adapter and retreive the view by findViewbyTag. I could not understand how to implement this and I am also not sure if that will work for FragmentStatePagerAdapter.
- I explored other options as suggested in various forums, but cannot make them work.
My code is very much same as in a android http://developer.android.com/training/animation/screen-slide.html. Basic components are the same (Fragment activity xml with some display components including a textview, viewpager xml with just a view pager in it, a Fragment class and the main FragmentActivity class). in my FragmentActivity class I have added a pageChangelistener to my viewpager so as I can do my textview changes during onPageSelected().
Any help is is appreciated.
Adding the code for reference.
Public class myActivity extends FragmentActivity
//Variable declarations
protected void onCreate(Bundle savedInstanceState) {
ViewPager mPager = (ViewPager) findViewById(R.id.pager);
mPagerAdapter = new ScreenSlidePagerAdapter(getSupportFragmentManager());
mPager.setAdapter(mPagerAdapter);
View CurrView;
OnPageChangeListener pageChangelistener = new OnPageChangeListener() {
#Override
public void onPageSelected(int pageSelected) {
doTextViewChnges();//access the text view and update it based on pageSelected
---THIS IS WHERE I AM STUCK IN TRYING TO GET THE TEXTVIEW IN MY CURRENT FRAGMWNT/VIEW-------
}
mPager.setOnPageChangeListener(pageChangelistener);
}
private class ScreenSlidePagerAdapter extends FragmentStatePagerAdapter {
public ScreenSlidePagerAdapter(android.support.v4.app.FragmentManager fm) {
super(fm);
}
#Override
public android.support.v4.app.Fragment getItem(int position) {
return ScreenSlidePageFragment.create(position, <other parameters I want to pass>);
}
#Override
public int getCount() {
return NUM_PAGES;
}
}
in your OnPageChangeListener:
OnPageChangeListener pageChangelistener = new OnPageChangeListener() {
#Override
public void onPageSelected(int pageSelected) {
ScreenSlidePageFragment currentFragment = (ScreenSlidePageFragment) mPagerAdapter.getItem(pageSelected)
doTextViewChnges();//access the text view and update it based on pageSelected
---THIS IS WHERE I AM STUCK IN TRYING TO GET THE TEXTVIEW IN MY CURRENT FRAGMWNT/VIEW-------
}
i assume you want to access your fragments from your activity and update the views.Fragments are parts of activities and their views can be accessed by them. Fragments are created dynamically in viewadapters and there is no straight forward and easy way to Tag them. You may simply access a fragment by its index number.first one is 0 second one is 1 and so on...
//access your fragment(in your case your first fragment)
//this part should be inside your doTextViewChnges()
Fragment fragment = (Fragment ) adapterViewPager
.getFragment(0);
if (fragment != null) {
//call a public method inside your fragment to update your text view
fragment .updateYourTextView();
}
Edit: inside your fragment create the following method and update your textview from there.
void updateYourTextView() {
yourTextView.setText("yourtext");
}