I'm having trouble with adding another page to the cwac-pager's ArrayPagerAdapter (v4). I had to use that library because I wasn't able to add a new tab dynamically using the system PagerAdapter.
MainActivity.java:
public class MainActivity extends AppCompatActivity implements TabLayout.OnTabSelectedListener {
private ViewPager viewPager;
private ArrayPagerAdapter pagerAdapter;
private TabLayout tabLayout;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
setSupportActionBar((Toolbar) findViewById(R.id.app_toolbar));
tabLayout = findViewById(R.id.tab_layout);
tabLayout.setTabGravity(TabLayout.GRAVITY_FILL);
viewPager = findViewById(R.id.pager);
pagerAdapter = new CustomPagerAdapter(getSupportFragmentManager(), new ArrayList<PageDescriptor>());
viewPager.setAdapter(pagerAdapter);
viewPager.addOnPageChangeListener(new TabLayout.TabLayoutOnPageChangeListener(tabLayout));
tabLayout.addOnTabSelectedListener(this);
}
#Override
public void onStart() {
super.onStart();
pagerAdapter.add(new TestPageDescriptor());
tabLayout.addTab(tabLayout.newTab().setText("Hello"));
// Uncomment the following lines to make the app crash
pagerAdapter.add(new TestPageDescriptor()); // CRASH
tabLayout.addTab(tabLayout.newTab().setText("Hello2"));
}
#Override
public void onStop() {
super.onStop();
// Remove all the tabs (required in my main application, not in this test)
tabLayout.removeAllTabs();
for (int i = 0; i < pagerAdapter.getCount(); i++) {
pagerAdapter.remove(i);
}
}
#Override
public void onTabSelected(TabLayout.Tab tab) {
viewPager.setCurrentItem(tab.getPosition());
}
#Override
public void onTabUnselected(TabLayout.Tab tab) {
}
#Override
public void onTabReselected(TabLayout.Tab tab) {
}
private class CustomPagerAdapter extends ArrayPagerAdapter<Fragment> {
CustomPagerAdapter(FragmentManager fragmentManager, List<PageDescriptor> descriptors) {
super(fragmentManager, descriptors);
}
#Override
protected Fragment createFragment(PageDescriptor desc) {
return new TestFragment();
}
}
private class TestPageDescriptor extends SimplePageDescriptor {
TestPageDescriptor() {
super("Test","TestHey");
}
}
}
TestFragment.java:
public class TestFragment extends Fragment {
#Override
public View onCreateView(#NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.test_frag, container, false);
}
}
activity_main.xml:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="me.test.myapplication.MainActivity"
android:orientation="vertical">
<android.support.v7.widget.Toolbar
android:id="#+id/app_toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
android:theme="#style/ThemeOverlay.AppCompat.ActionBar"
app:popupTheme="#style/ThemeOverlay.AppCompat.Light"
app:title="#string/app_name" />
<android.support.design.widget.TabLayout
android:id="#+id/tab_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/colorPrimary"
android:elevation="4dp"
android:minHeight="?attr/actionBarSize"/>
<android.support.v4.view.ViewPager
android:id="#+id/pager"
android:layout_width="match_parent"
android:layout_height="fill_parent" />
</LinearLayout>
test_frag.xml: just a LinearLayout with a View, no matter which.
In MainActivity.java see the comments I added in onStart(): if you run the app with just one tab (last two lines of the method commented), the activity will start correctly. However, if you add another tab by uncommenting the lines, the app will crash immediately. What am I doing wrong?
Thanks
Edit: logcat
I/Process: Sending signal. PID: 3877 SIG: 9
Application terminated.
No exceptions, no errors.
The fragment tags need to be unique, as is covered in the library documentation. So, as Matt Clark pointed out, you need to use different tags for your different pages.
Note that you do not need to create your own subclass of SimplePageDescriptor, at least in the code from your question. You could just use SimplePageDescriptor directly.
Related
I have multiple activities and fragments. I would like to set toolbar in BaseActivity (so set it only once). But need an acces to the toolbar from fragment (method like - show, hide, changeTitle etc.)
Any suggestion?
I have tried solution below, but when I want to hide fragment, NPException is shown
public abstract class BaseActivity extends AppCompatActivity {
Toolbar toolbar;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(getLayoutResource());
configureToolbar();
}
protected abstract int getLayoutResource();
private void configureToolbar() {
toolbar = (Toolbar) findViewById(R.id.toolbar);
if (toolbar != null) {
setSupportActionBar(toolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
}
}
public void hideToolbar() {
toolbar.setVisibility(View.GONE);
}
My activity
public class MyActivity extends BaseActivity() {
}
I call hideToolbar in fragment like:
public class MyFragment extends Fragment() {
onCreate() {
((Myactivity)getActivity).hideToolbar();
}
I have include something like yours in my project. This is sample. You can take reference from it.
BaseActivity.java class:
public abstract class BaseActivity extends AppCompatActivity {
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(getLayoutResource());
}
protected abstract int getLayoutResource();
}
ToolBarActivity.java class:
public abstract class ToolbarActivity extends BaseActivity {
protected Toolbar toolbar;
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
View contentView = findViewById(R.id.flToolbarContentContainer);
if (contentView instanceof ViewGroup) {
((ViewGroup) contentView)
.addView(LayoutInflater.from(this)
.inflate(getToolbarLayoutResource()
, (ViewGroup) contentView, false));
}
toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
}
#Override
protected int getLayoutResource() {
return R.layout.activity_toolbar;
}
protected abstract int getToolbarLayoutResource();
public void showToolbar() {
toolbar.setVisibility(View.VISIBLE);
}
public void hideToolbar() {
toolbar.setVisibility(View.GONE);
}
}
activity_toolbar.xml layout file:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/rlToolbarContainer"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:layout_toLeftOf="#+id/pbToolbarActivity"
android:background="#color/blue_panel_day_background"
android:theme="#style/ToolbarTheme" />
<FrameLayout
android:id="#+id/flToolbarContentContainer"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="#id/toolbar" />
</RelativeLayout>
MainActivity.java class:
public class MainActivity extends ToolbarActivity {
#Override
protected int getToolbarLayoutResource() {
return R.layout.activity_main;
}
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
homeFragment = HomeFragment.newInstance();
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.llMainActivityContainer, homeFragment)
.commit();
}
}
activity_main.xml layout file:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/llMainActivityContainer"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
</LinearLayout>
Now, in Fragment class, apply:
((ToolbarActivity) getActivity()).showToolbar();
((ToolbarActivity) getActivity()).hideToolbar();
From Fragment
getActivity().getSupportActionBar();
Use:
public abstract class BaseActivity extends AppCompatActivity {
private Toolbar toolbar;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(getLayoutResource());
configureToolbar();
}
protected abstract int getLayoutResource();
private void configureToolbar() {
toolbar = (Toolbar) findViewById(R.id.toolbar);
// changes made here, try to find if getSupportActionBar() is null or not after setting it - setSupportActionBar
setSupportActionBar(toolbar);
if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
}
}
// How and where you calling this method ?
public void hideToolbar() {
toolbar.setVisibility(View.GONE);
}
To access ActionBar from Fragment try the below method
ActionBar actionBar = ((AppCompatActivity) getActivity()).getSupportActionBar();
You can now access all the methods of ActionBar
I am trying to add DataBinding to my app. In my app, I have a BaseActivity which has a Toolbar and a FrameLayout. FrameLayout is container for activities' which extend the BaseActivity. How can I add databinding to both my BaseActivity and the extending activities?
I'll share my code without DataBinding:
Here is my BaseActivity.java:
public class BaseActivity extends AppCompatActivity {
#Override
public void setContentView(#LayoutRes int layoutResID) {
LinearLayout container = (LinearLayout) getLayoutInflater().inflate(R.layout.activity_base, null);
FrameLayout activityContent = (FrameLayout) container.findViewById(R.id.activityContent);
getLayoutInflater().inflate(layoutResID, activityContent, true);
super.setContentView(container);
Toolbar toolbar = (Toolbar) container.findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
}
#Override
public boolean onOptionsItemSelected(MenuItem menuItem) {
if (menuItem.getItemId() == android.R.id.home) {
finish();
}
return super.onOptionsItemSelected(menuItem);
}
public void setTitle(String title) {
getSupportActionBar().setTitle(title);
}
}
Here is activity_base.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<include
android:id="#+id/toolbarLayout"
layout="#layout/toolbar" />
<FrameLayout
android:id="#+id/activityContent"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
</layout>
Here is the activity which extends BaseActivity: CardRecyclerViewActivity.java:
public class CardRecyclerViewActivity extends BaseActivity {
RecyclerView recyclerView;
ReplikAdapter adapter;
ArrayList<Replik> replikListesi;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_card_recyclerview);
replikListesiniDoldur();
adapter = new ReplikAdapter(replikListesi, this, R.layout.item_card_replik);
recyclerView = (RecyclerView) findViewById(R.id.recyclerView);
recyclerView.setAdapter(adapter);
setTitle(R.string.cardRecylerView);
}
public void replikListesiniDoldur() {
replikListesi = new ArrayList<Replik>();
replikListesi.add(new Replik(R.drawable.harvey1, "Ben ihtimallere oynamam. Adama oynarım.", "Harvey Specter"));
replikListesi.add(new Replik(R.drawable.harvey2, "Avukatlık doktorluğa çok benzer, acıtana kadar bastırırsın ve böylece nereye bakman gerektiğini anlarsın.", "Harvey Specter"));
replikListesi.add(new Replik(R.drawable.harvey3, "İşte aramızdaki fark bu; Sen küçük kaybetmek istiyorsun ben ise büyük kazanmak.", "Harvey Specter"));
replikListesi.add(new Replik(R.drawable.harvey4, "Benim hayallerim yok, hedeflerim var.", "Harvey Specter"));
}
}
Okey, I add DataBinding to my BaseActivity.java like that:
public class BaseActivity extends AppCompatActivity {
#Override
public void setContentView(#LayoutRes int layoutResID) {
ActivityBaseBinding binding = DataBindingUtil.inflate(getLayoutInflater(), R.layout.activity_base, null, false);
getLayoutInflater().inflate(layoutResID, binding.activityContent, true);
super.setContentView(binding.getRoot());
setSupportActionBar(binding.toolbarLayout.toolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
}
#Override
public boolean onOptionsItemSelected(MenuItem menuItem) {
if (menuItem.getItemId() == android.R.id.home) {
finish();
}
return super.onOptionsItemSelected(menuItem);
}
public void setTitle(String title) {
getSupportActionBar().setTitle(title);
}
}
activity_base.xml with DataBinding:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<include
android:id="#+id/toolbarLayout"
layout="#layout/toolbar" />
<FrameLayout
android:id="#+id/activityContent"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
</layout>
But I am not sure how to add DataBinding to my extending activities in this scenerio. Could you help me out, please?
Might be late for this question.
Your BaseActivity should looks like following.
public abstract class BaseActivity<B extends ViewDataBinding, T extends BaseViewModel> extends AppCompatActivity {
protected B dataBinding;
protected T baseViewModel;
protected void bindView(int layoutId) {
dataBinding = DataBindingUtil.setContentView(this, layoutId);
}
#Override
protected void onDestroy() {
baseViewModel.detachView();
super.onDestroy();
}
}
Your MainActivity should looks like following.
public class MainActivity extends BaseActivity<ActivityMainBinding, MainActivityViewModel> implements MainActivityView, View.OnClickListener {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
bindView(R.layout.activity_main);
baseViewModel = new MainActivityViewModel();
baseViewModel.attachView(this);
dataBinding.btnPerformOperation.setOnClickListener(this);
}
#Override
public void OnDataLoad(String item) {
dataBinding.setIsLoading(false);
startActivity(new Intent(MainActivity.this, SecondActivity.class).putExtra("result", item));
}
#Override
public void OnError(Throwable throwable) {
}
#Override
public void onClick(View v) {
dataBinding.setIsLoading(true);
baseViewModel.loadNextData();
}
}
Reference source code can be download from here.
Hope this helps you.
how about this?
BaseActivityViewModel.java
public class BaseActivityViewModel {
public void onClick() {
Log.i("BaseActivityViewModel | onClick", "111111111111");
}
}
activity_base.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="model"
type="kr.changhoonjin.textmvvmwithextend.BaseActivityViewModel"/>
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="#{()->model.onClick()}"
android:text="base"/>
<FrameLayout
android:id="#+id/layout_container"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
</layout>
BaseActivity.java
public class BaseActivity extends AppCompatActivity {
private ActivityBaseBinding binding;
#Override protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = DataBindingUtil.setContentView(this, R.layout.activity_base);
binding.setModel(new BaseActivityViewModel());
}
protected <T extends ViewDataBinding> T putContentView(#LayoutRes int resId) {
return DataBindingUtil.inflate(getLayoutInflater(), resId, binding.layoutContainer, true);
}
}
MainActivityViewModel.java
public class MainActivityViewModel {
public void onClick2() {
Log.i("MainActivityViewModel | onClick2", "2222222222222");
}
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="model"
type="kr.changhoonjin.textmvvmwithextend.MainActivityViewModel"/>
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="#{()->model.onClick2()}"
android:text="main"/>
</LinearLayout>
</layout>
MainActivity.java
public class MainActivity extends BaseActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityMainBinding binding = putContentView(R.layout.activity_main);
binding.setModel(new MainActivityViewModel());
}
}
It will be same as you have done in BaseActivity, instead of inflate just use setContentView
private ActivityCardRecyclerviewBinding binding;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = DataBindingUtil.setContentView(this,R.layout.activity_card_recyclerview);
}
I have a static v4.app.Fragment in an AppCompatActivity's xml, and everything shows up when I launch the app, but once I enter another activity and comeback, or switch to another app and switch back, the fragment cannot be seen, while the Log.d of the fragment shows that the fragment exists somewhere. I have also tried using getsupportfragmentmanager.add.commit to programmatically add the fragment, and it has the same result
activity_main.xml
...
<FrameLayout
android:id="#+id/content_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="#string/appbar_scrolling_view_behavior">
<fragment
android:id="#+id/fragment"
android:name="xxx.xxx.xxx.Fragment"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</FrameLayout>
<android.support.design.widget.FloatingActionButton
...
MainActivity.java
#BindView(R.id.toolbar) Toolbar toolbar;
#BindView(R.id.fab) FloatingActionButton fab;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(MainActivity.this);
...
No onResume, onCreateView, etc
XFragment.java
public class XFragment extends Fragment {
#BindView(R.id.swipeRefreshLayout) SwipeRefreshLayout swipeRefreshLayout;
#BindView(R.id.recyclerView) RecyclerView recyclerView;
// and a lot of other variables
public XFragment() {
}
public static XFragment newInstance(boolean isSearch) {
XFragment xFragment = new XFragment();
return xFragment;
}
#Override
public void onResume() {
Log.d(TAG, "onResume");
super.onResume();
//initiate variables
inflateRecyclerView();
//etc
}
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, "onCreate");
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
Log.d(TAG, "onCreateView");
View view = inflater.inflate(R.layout.fragment_x, container, false);
ButterKnife.bind(this, view);
return view;
}
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
Parcelable parcelable = llm.onSaveInstanceState();
outState.putParcelable("LM", parcelable);
}
#Override
public void onViewStateRestored(#Nullable Bundle savedInstanceState) {
Log.d(TAG, "onViewStateRestored");
super.onViewStateRestored(savedInstanceState);
if (savedInstanceState != null) {
llm = savedInstanceState.onRestoreInstanceState(savedInstanceState.getParcelable("LM"));
}
}
public void inflateRecyclerView() {
//use retrofit to call api
}
P.S.: when I add it dynamically, I hide the , findViewById in the activity, then use
XFragment f = new XFragment.newInstance();
getSupportFragmentManager().beginTransaction()
.add(R.id.fragment_container, f).commit();
Sorry, I know what is causing the problem. I have been using a library call DynamicBox, and it seems that it is causing the problem. I don't know if I have do something wrong or there are bugs on that library yet.
Yay...
I'm trying to use a SwipeToRefreshLayout to update some information inside a marker. Although I use android:layout_width="wrap_content" to keep the content size, for some reason it always fills the parent, leaving a huge space below the information content. What am I doing wrong?
By the way, if I don't have the SwipeRefreshLayout , and use only the ScrollView, that empty space doesn't appear.
Here is my XML code:
<android.support.v4.widget.SwipeRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/svMarker"
android:layout_width="wrap_content"
android:layout_height="wrap_content" >
<ScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/svMarkerInfo">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:id="#+id/llMarkerInfo">
</LinearLayout>
</ScrollView>
</android.support.v4.widget.SwipeRefreshLayout>
Thank you for your answers!
I leave also the image of how is being showed using the SwipeRefreshLayout. Behind the marker information there is a map:
If I dont use SwipeRefreshLayout, the marker information is shown the next way, which visually is what I am looking for. but I'm missing the SwipeToRefresh update:
The SwipeRefreshLayout will disabled while dragging a map by using a listener and the refresh will only take place if you swipe down on the second half ( i.e. the layout below map). To get map drag events, we need to do some workaround because there is no direct support from Google and this library MapStateListener has done that. Just paste these three classes MapStateListener.java, TouchableMapFragment.java, TouchableWrapper.java into your project.
Here is a working example.
MainActivity.java
public class MainActivity extends AppCompatActivity implements OnMapReadyCallback
{
private SwipeRefreshLayout swipeRefreshLayout;
private View secondHalf;
private TouchableMapFragment mMap;
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
swipeRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.svMarker);
secondHalf = findViewById(R.id.secondHalf);
setUpMap();
swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener()
{
#Override
public void onRefresh()
{
// A thread to keep RefreshLayout busy for 5 seconds
if(!swipeRefreshLayout.isRefreshing())
new LoadImagesTask().execute();
}
});
}
public void setUpMap()
{
try
{
if (mMap == null)
{
mMap = ((TouchableMapFragment) getSupportFragmentManager()
.findFragmentById(R.id.firstHalf));
mMap.getMapAsync(this);
}
}
catch (Exception ex)
{
ex.printStackTrace();
}
}
#Override
public void onMapReady(GoogleMap googleMap)
{
new MapStateListener(googleMap, mMap, this)
{
#Override
public void onMapTouched()
{
// Map touched
swipeRefreshLayout.setEnabled(false);
}
#Override
public void onMapReleased() {
// Map released
swipeRefreshLayout.setEnabled(true);
}
#Override
public void onMapUnsettled() {
// Map unsettled
}
#Override
public void onMapSettled() {
// Map settled
}
};
}
private class LoadImagesTask extends AsyncTask<Void, Void, Void>
{
#Override
protected void onPreExecute()
{
super.onPreExecute();
}
#Override
protected Void doInBackground(Void... voids)
{
try
{
for (int i = 1; i <= 5; i++)
{
Thread.sleep(1000);
}
}
catch (Exception ex)
{
ex.printStackTrace();
}
return null;
}
#Override
protected void onPostExecute(Void aVoid)
{
super.onPostExecute(aVoid);
swipeRefreshLayout.setRefreshing(false);
}
}
}
main.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.SwipeRefreshLayout android:id="#+id/svMarker"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:weightSum="2">
<fragment
android:id="#+id/firstHalf"
android:name="com.esealed.watermeterclient.TouchableMapFragment"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
/>
<ScrollView
android:id="#+id/secondHalf"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:background="#color/colorAccent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<!-- Other views comes here -->
</LinearLayout>
</ScrollView>
</LinearLayout>
</android.support.v4.widget.SwipeRefreshLayout>
The only solution I could figure out was defining the SwipeRefreshLayout size dinamically. No need to change my XML file.
On my MapActivity.class, where I manage the marker's info, every time I show a new marker information, I call the next method:
LinearLayout llMarkerInfo = (LinearLayout) findViewById(R.id.llMarkerInfo);
SwipeRefreshLayout svMarker = (SwipeRefreshLayout) findViewById(R.id.svMarker);
//First i register to the listener so I can get the measured size of the marker information's layout
ViewTreeObserver vto = llMarkerInfo.getViewTreeObserver();
vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
//I get the measured width and height of the marker information's layout
int width = llMarkerInfo.getMeasuredWidth();
int height = llMarkerInfo.getMeasuredHeight();
//Set that size to the SwipeToRefresh layout
LinearLayout.LayoutParams svParams = new LinearLayout.LayoutParams(width, height);
svMarker.setLayoutParams(svParams);
//Unregister the listener so is not called many times
if (Build.VERSION.SDK_INT < 16)
llMarkerInfo.getViewTreeObserver().removeGlobalOnLayoutListener(this);
else
llMarkerInfo.getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
});
This way, I define exactly the size my marker information's layout has, to my SwipeRefreshLayout, dinamically.
Image with the final result (expected = reality):
Short version:
I have a fragment that maintains a ViewPager for displaying two other fragments, let's call them FragmentOne and FragmentTwo. When starting the app FragmentOne is visible and FragmentTwo is off-screen, becoming visible only as one swipes the view to the left.
Normally onStart() and onResume() get invoked immediately for both fragments as soon as the app gets started.
The problem I have is when FragmentOne starts a custom Loader then onResume() does not get called on FragmentTwo until it becomes fully visible.
Questions:
Is this a problem with my code or a bug in the Android Support Library? (The problem did not occur with revision 12 of the library, it started with revision 13.)
If it's a bug in revisons 13 and 18, is there a workaround?
Is there something wrong with my custom Loader?
Long version:
I have built a sample application that demonstrates the problem. I have tried to reduce the code to the bare minimum but it's still a lot so please bear with me.
I have a MainActivity that loads a MainFragment which creates a ViewPager. It is important for my app that the ViewPager is maintained by a Fragment instead of an Activity.
MainFragment creates a FragmentPagerAdapter that in turn creates the fragments FragmentOne and FragmentTwo.
Let's start with the interesting bit, the two fragments:
FragmentOne is a ListFragment that uses a custom Loader to load the content:
public class FragmentOne extends ListFragment implements LoaderCallbacks<List<String>> {
private ArrayAdapter<String> adapter;
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
adapter = new ArrayAdapter<String>(getActivity(), android.R.layout.simple_list_item_1);
setListAdapter(adapter);
setEmptyText("Empty");
}
#Override
public void onResume() {
super.onResume();
// initializing the loader seems to cause the problem!
getLoaderManager().initLoader(0, null, this);
}
#Override
public Loader<List<String>> onCreateLoader(int id, Bundle args) {
return new MyLoader(getActivity());
}
#Override
public void onLoadFinished(Loader<List<String>> loader, List<String> data) {
adapter.clear();
adapter.addAll(data);
}
#Override
public void onLoaderReset(Loader<List<String>> loader) {
adapter.clear();
}
public static class MyLoader extends AsyncTaskLoader<List<String>> {
public MyLoader(Context context) {
super(context);
}
#Override
protected void onStartLoading() {
forceLoad();
}
#Override
public List<String> loadInBackground() {
return Arrays.asList("- - - - - - - - - - - - - - - - - - - foo",
"- - - - - - - - - - - - - - - - - - - bar",
"- - - - - - - - - - - - - - - - - - - baz");
}
}
}
It is that Loader that seems to cause the problem. Commenting out the initLoader line makes the fragment life-cycle work as expected again.
FragmentTwo changes its content based on whether onResume() has been invoked or not:
public class FragmentTwo extends Fragment {
private TextView text;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
text = new TextView(container.getContext());
text.setText("onCreateView() called");
return text;
}
#Override
public void onResume() {
super.onResume();
Log.i("Fragment2", "onResume() called");
text.setText("onResume() called");
}
}
And here is the boring rest of the code.
MainActivity:
public class MainActivity extends FragmentActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Fragment fragment = new MainFragment();
getSupportFragmentManager().beginTransaction().add(R.id.container, fragment).commit();
}
}
Layout activity_main:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent" />
MainFragment:
public class MainFragment extends Fragment {
private ViewPager viewPager;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View layout = inflater.inflate(R.layout.frag_master, container, false);
viewPager = (ViewPager) layout.findViewById(R.id.view_pager);
return layout;
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
viewPager.setAdapter(new MyPagerAdapter(getChildFragmentManager()));
}
private static final class MyPagerAdapter extends FragmentPagerAdapter {
public MyPagerAdapter(FragmentManager fragmentManager) {
super(fragmentManager);
}
#Override
public int getCount() {
return 2;
}
#Override
public Fragment getItem(int position) {
if (position == 0)
return new FragmentOne();
else
return new FragmentTwo();
}
}
}
Layout frag_master:
<android.support.v4.view.ViewPager xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/view_pager"
android:layout_width="match_parent"
android:layout_height="match_parent" />
It appears to be a bug in support library. The change below solves the issue.
// FragmentOne.java
#Override
public void onResume() {
super.onResume();
Handler handler = getActivity().getWindow().getDecorView().getHandler();
handler.post(new Runnable() {
#Override public void run() {
// initialize the loader here!
getLoaderManager().initLoader(0, null, FragmentOne.this);
}
});
}
Another workaround that worked for me was to use the MainFragment's loader manager:
getParentFragment().getLoaderManager().initLoader(0, null, this);