Feckless III: Fragments in Fragments in Fragments

My latest update, to me, doesn’t look like much, but I feel like I accomplished quite a bit. It caused a lot of frustration, took an immense number of google searches, and ate up so, so much time. But because I dealt with all of it, I doubt I’ll ever be intimidated again by Fragments, Navigation Drawers, or ViewPagers.

GitHub

What’s new

The stopwatch works exactly as before, but now I’ve got I’ve got a navigation drawer. The drawer will take users to a viewPager with tabs for each day of the week. The pager will open on the tab of whatever day is clicked. Right now, the tabs just contain a textView with a day of the week, but soon they’ll have a textView which contains some amount of time.

What was easy

The only thing I can think of that didn’t cause me a headache was moving the stopwatch from the main activity to it’s own fragment. The only difference was that I since it was a fragment, I couldn’t use the onClick xml properties, so I had to set my own onClickListener’s in code. To speed that up a bit, I used butterknife. I’ll use my runButton as an example.

A quick declaration:

@BindView(R.id.run_button)Button btnRun;

Binding in OnCreateView(only need one statement for all views):

ButterKnife.bind(this,view);

And the addition of an annotation to my method:

@OnClick(R.id.run_button)
public void onClickRun(){
if(running){
running = false;
btnRun.setText(R.string.start);
}else{
running = true;
btnRun.setText(R.string.pause);
}
}

What was hard:

The first big hurdle was sort of just figuring out how the project would be structured. Instead of having multiple activities like I’ve had in the past, I wanted one navigation drawer and one toolbar, that would remain static throughout the app. And all my content would be contained in fragments that change dynamically.

I restructured my main activity, and took out all the stopwatch elements:

<android.support.v4.widget.DrawerLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/drawer"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
>

<android.support.design.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
>

<android.support.design.widget.AppBarLayout
android:id="@+id/appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/AppTheme.AppBarOverlay"
>

<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="@style/AppTheme.PopupOverlay"
/>

</android.support.design.widget.AppBarLayout>

<include layout="@layout/content_main"/>


</android.support.design.widget.CoordinatorLayout>

<android.support.design.widget.NavigationView
android:id="@+id/nav_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
android:fitsSystemWindows="true"
app:headerLayout="@layout/navheader"
app:menu="@menu/menu_navigation"
/>
</android.support.v4.widget.DrawerLayout>

The content main layout is just a single frameLayout. There’s a fragment transaction in OnCreate that handle’s the frameLayout’s initial display:

FragmentManager fm = getSupportFragmentManager();
Fragment fragment = fm.findFragmentById(R.id.fragmentContainer);
if (fragment == null) {
fragment = new StopwatchFragment();
fm.beginTransaction()
.add(R.id.fragmentContainer, fragment)
.commit();
}

The fragment is changed from then on, by user interaction. Here is the Navigation Drawer’s selection method to do that:

public void selectDrawerItem(MenuItem menuItem) {
// Create a new fragment and specify the fragment to show based on nav item clicked
Fragment fragment = null;
int args = 0;
Class fragmentClass;
switch(menuItem.getItemId()) {
case R.id.stopwatch:
fragmentClass = StopwatchFragment.class;
break;
case R.id.mon:
fragmentClass = PagerFragment.class;
args = 0;
break;
case R.id.tue:
fragmentClass = PagerFragment.class;
args = 1;
break;
case R.id.wed:
fragmentClass = PagerFragment.class;
args = 2;
break;
case R.id.thu:
fragmentClass = PagerFragment.class;
args = 3;
break;
case R.id.fri:
fragmentClass = PagerFragment.class;
args = 4;
break;
case R.id.sat:
fragmentClass = PagerFragment.class;
args = 5;
break;
case R.id.sun:
fragmentClass = PagerFragment.class;
args = 6;
break;
case R.id.total:
fragmentClass = TotalFragment.class;
break;
default:
fragmentClass = StopwatchFragment.class;
}

try {
fragment = (Fragment) fragmentClass.newInstance();
} catch (Exception e) {
e.printStackTrace();
}

Bundle bundle = new Bundle();
bundle.putInt("tab", args);
fragment.setArguments(bundle);

// Insert the fragment by replacing any existing fragment
FragmentManager fm = getSupportFragmentManager();
fm.beginTransaction().replace(R.id.fragmentContainer, fragment).commit();

setTitle(menuItem.getTitle());
}

The method above, in addition to switching fragment in my container, send’s some data along with my fragments. Sending data between fragments was another concept that briefly beguiled me. I had come across it in practice before, so it was easy to remember and implement after just a bit of research. In the example above, I send an integer argument along with all pagerFragments, that let’s the pager know which tab to open to.

This is how the pagerFragment retrieves that data in it’s oncreateView:

Bundle bundle = this.getArguments();

viewPager.setCurrentItem(bundle.getInt("tab", 0));

I use a similar approach in the pagerFragment’s setup method, which initially set’s up the pager and it’s tabs. This time it sends a Day object to the fragment that the viewPager is going to host. The Day objects are static in my app.

private void setUpViewPager(ViewPager viewPager) {
DayFragmentAdapter adapter = new DayFragmentAdapter(getActivity().getSupportFragmentManager());
for (int i = 0; i < Day.days.length; i++) {
DayFragment fragment = new DayFragment();
Bundle bundle = new Bundle();
bundle.putParcelable(DAY_OBJECT, Day.days[i]);
fragment.setArguments(bundle);
adapter.addFragment(fragment, Day.days[i].getDay());
}

viewPager.setAdapter(adapter);
}

One big issue I had was pretty easy to solve, but it took me a while to figure out. The fragments contained in my ViewPager fragment were overlapping the tabLayout. The tabs were also not responding to touch, so I also spent a while trying to find a fix for that. I realized though, that the issue was being caused by the overlap as well. I tried lots of complicated fixes, and none of them worked. I almost gave up, and was going to try to work around it. In another round of googling though, I saw a post mention linear layouts and a light bulb went on in my head. It wasn’t the first time I had seen this BTW, but for some reason, because I was using fragments within fragments, I didn’t think this applied to my project. For whatever reason, it made sense this time, and that was all it took.

What’s next

I need to populate my fragment’s with the correct data, and set up that FAB to save times.

Like what you read? Give Alex Lowe a round of applause.

From a quick cheer to a standing ovation, clap to show how much you enjoyed this story.