Android UI: Taking a look at iosched open source app
So, yes, open source is right, great and helps everyone to learn how to do right things (or even to just learn more about some framework). This week I needed to look at the source code of two open source apps for Android: iosched and ubuntu one for android. They’re both pretty great and they deserve attention and take a closer look to their source code. Now, I’m going to talk you about a nice implementation I found on IOSched app. The SinglePane pattern. I really didn’t find it very useful first time, but when you start using it, you find it really cool! It provides you a simple way to define Activities that just have one fragment for its content. Simple, uh?
/** * Copyright 2012 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.android.apps.iosched.ui; import com.google.android.apps.iosched.R; import android.content.Intent; import android.os.Bundle; import android.support.v4.app.Fragment; /** * A {@link BaseActivity} that simply contains a single fragment. The intent used to invoke this * activity is forwarded to the fragment as arguments during fragment instantiation. Derived * activities should only need to implement {@link SimpleSinglePaneActivity#onCreatePane()}. */ public abstract class SimpleSinglePaneActivity extends BaseActivity { private Fragment mFragment; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_singlepane_empty); if (getIntent().hasExtra(Intent.EXTRA_TITLE)) { setTitle(getIntent().getStringExtra(Intent.EXTRA_TITLE)); } final String customTitle = getIntent().getStringExtra(Intent.EXTRA_TITLE); setTitle(customTitle != null ? customTitle : getTitle()); if (savedInstanceState == null) { mFragment = onCreatePane(); mFragment.setArguments(intentToFragmentArguments(getIntent())); getSupportFragmentManager().beginTransaction() .add(R.id.root_container, mFragment, "single_pane") .commit(); } else { mFragment = getSupportFragmentManager().findFragmentByTag("single_pane"); } } /** * Called in <code>onCreate</code> when the fragment constituting this activity is needed. * The returned fragment's arguments will be set to the intent used to invoke this activity. */ protected abstract Fragment onCreatePane(); public Fragment getFragment() { return mFragment; } }
Although this time, it extends from BaseActivity, you could extend from Activity, SherlockFragmentActivity, RoboSherlockFragmentActivity or whatever fits your project. Just take into account that It must be able to use fragments.
As you can see it’s pretty simple, you just have to extend this class and override onCreatePane() on your child activity.
public class ExampleOneFragmentActivity extends SinglePaneActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); } @Override protected Fragment onCreatePane() { return new ExampleOneFragment(); } }
and this would be the fragment:
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <LinearLayout android:orientation="vertical" android:layout_width="match_parent" android:layout_height="wrap_content" android:paddingLeft="@dimen/content_padding_normal" android:paddingRight="@dimen/content_padding_normal" android:paddingTop="@dimen/content_padding_normal" android:paddingBottom="@dimen/content_padding_normal"> <TextView android:id="@+id/vendor_name" android:layout_width="match_parent" android:layout_height="wrap_content" style="@style/TextHeader" /> <TextView android:id="@+id/vendor_url" android:layout_width="match_parent" android:layout_height="wrap_content" android:autoLink="web" android:paddingBottom="@dimen/element_spacing_normal" style="@style/TextBody" /> <com.google.android.apps.iosched.ui.widget.BezelImageView android:id="@+id/vendor_logo" android:scaleType="centerCrop" android:layout_width="@dimen/vendor_image_size" android:layout_height="@dimen/vendor_image_size" android:layout_marginTop="@dimen/element_spacing_normal" android:src="@drawable/sandbox_logo_empty"/> <TextView android:id="@+id/vendor_desc" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="@dimen/element_spacing_normal" android:paddingTop="@dimen/element_spacing_normal" style="@style/TextBody" /> </LinearLayout> </ScrollView>
*That one is picked from iosched
Don’t forget that this simple pattern also provides passing extra to set a new title! Or you can use this two methods from BaseActivity to pass arguments from activities to fragments.
/** * Converts an intent into a {@link Bundle} suitable for use as fragment arguments. */ public static Bundle intentToFragmentArguments(Intent intent) { Bundle arguments = new Bundle(); if (intent == null) { return arguments; } final Uri data = intent.getData(); if (data != null) { arguments.putParcelable("_uri", data); } final Bundle extras = intent.getExtras(); if (extras != null) { arguments.putAll(intent.getExtras()); } return arguments; } /** * Converts a fragment arguments bundle into an intent. */ public static Intent fragmentArgumentsToIntent(Bundle arguments) { Intent intent = new Intent(); if (arguments == null) { return intent; } final Uri data = arguments.getParcelable("_uri"); if (data != null) { intent.setData(data); } intent.putExtras(arguments); intent.removeExtra("_uri"); return intent; }