Hello,
After my Hebrew blog posts started to get published on the wonderful iAndroid website i decided to transform this blog into the English edition.
Slowly but surely i'll translate my earlier posts to English but i wanted its debut will be with a fresh post so here it is, i hope you will like it.
For those of you who don't know, all of my posts until now (including this one) or derived from AndconLab; the developer code lab that +Ran Nachmany, +Amir Lazarovich and myself created last year for the great MultiScreenX convention we helped organize.
Motivation
Ladies and gentlemen, i'm proud to announce the demise of Android as a mobile phone OS and i am more than proud to announce the birth of Android as an all round OS.
How can i say that?
Simply!
Most of your potential clients have Android running on screens that range from the size of 2" (a smartwatch) all the way to 100" (a T.V. set) and from QHD to 4K.
In the past you could have dealt with this kind of situation in one of three ways:
- Ignore the issue (as most of you did).
- Create a vast and complicated mechanism to figure out and adjust your layouts dynamically at runtime.
- Create different apps per device and use the Google Play to slice up the market.
All of the above had great disadvantages and when Google figured that out (2 years ago) it created a better way, called Fragments, to adjust yourself to any situation with grace, ease and not a lot of coding.
How does it work?
Android has always "known" what the device's screen size and resolution is but until Honeycomb (3.X, which was made specifically for tablets) it had a limited use "out of the box", the smallest Android unit was the Activity which had a .java implementation and an .xml layout and it symbolized 1 full screen of the device (no matter what size it was).The concept of Fragments is that it's an implementation until which is smaller or equal to the activity in terms of screen usage and that it has a dynamic manager which can push or pop Fragments dynamically from an Activity so an Activity could display one or more Fragments at the same time while adjusting to any screen with a low memory and runtime overhead and with the minimum amount of development.
Current market state (why are you not using it?!)
Warning: the author of this post is about to use name dropping to make himself look important, please disregard that :)
I recently met Mr. Reto Meier who claimed that developers categorize most tasks into two categories:
- "This will take less than a minute"
- "This will take forever".
He continued to claim that most people will put off items from category #2 until they will check-off all the items in category #2 but category #1 will never be empty so they will only do an item from #2 when it's absolutely impossible to continue without it.
Sounds familiar?
The only problem with that is that he argued that the real-life definitions of the aforementioned categories are closer to these:
- "This will take roughly 30 minutes".
- "This will be longer than 30 minutes".
which would implies that:
a. forever = more than 30 minutes :)
b. tasks that seem longer than half an hour to execute will never get done.
Now, given that most tasks that will improve your product in a big way will probably take more than 30 minutes would suggest that a rational developer will do these ASAP rather than putting them off for later, right?
Wrong?
Even if you're reading this while mumbling to yourselves, "what is this non-sense?",at least you're thinking about it so that's good.
Step 0 - What are we starting with?
A classic example of a ListView Activity:
public class MainActivity extends SherlockActivity implements OnItemClickListener{
private ListView mList;
private ProgressDialog mProgressDialog;
private BroadcastReceiver mUpdateReceiver;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main_activity);
mList = (ListView) findViewById(R.id.list);
mList.setOnItemClickListener(this);
}
@Override
protected void onPause() {
super.onPause();
if (null != mUpdateReceiver) {
unregisterReceiver(mUpdateReceiver);
mUpdateReceiver = null;
}
}
@Override
protected void onResume() {
super.onResume();
mUpdateReceiver = new BroadcastReceiver() {
//TODO: [Ran] handle network failure
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equalsIgnoreCase(CommunicationService.RESULTS_ARE_IN)) {
new lecturesLoader().execute((Void) null);
}
if (null != mProgressDialog)
mProgressDialog.dismiss();
}
};
final IntentFilter filter = new IntentFilter();
filter.addAction(CommunicationService.RESULTS_ARE_IN);
registerReceiver(mUpdateReceiver, filter);
new lecturesLoader().execute((Void)null);
}
@Override
public void onItemClick(AdapterView list, View view, int position, long id) {
Intent i = new Intent(this,SingleLectureActivity.class);
i.putExtra(SingleLectureActivity.EXTRA_LECTURE_ID, id);
startActivity(i);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getSupportMenuInflater().inflate(R.menu.main, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.action_refresh:
refreshList(true);
return true;
default:
return super.onOptionsItemSelected(item);
}
}
private void refreshList(boolean showProgressbar) {
if (showProgressbar) {
mProgressDialog = ProgressDialog.show(this, getString(R.string.progress_dialog_starting_title), getString(R.string.progress_dialog_starting_message));
}
Intent i = new Intent(this,CommunicationService.class);
startService(i);
// ServerCommunicationManager.getInstance(getApplicationContext()).startSearch("Android", 1);
}
////////////////////////////////
// Async task that queries the DB in background
////////////////////////////////
private class lecturesLoader extends AsyncTask {
private SQLiteDatabase db;
@Override
protected Cursor doInBackground(Void... params) {
db = new DatabaseHelper(MainActivity.this.getApplicationContext(), DatabaseHelper.DB_NAME,null , DatabaseHelper.DB_VERSION).getReadableDatabase();
return DBUtils.getAllLectures(db);
}
@Override
protected void onPostExecute(Cursor result) {
if (0 == result.getCount()) {
//we don't have anything in our DB, force network refresh
refreshList(true);
}
else {
LecturesAdapter adapter = (LecturesAdapter) mList.getAdapter();
if (null == adapter) {
adapter = new LecturesAdapter(MainActivity.this.getApplicationContext(), result);
mList.setAdapter(adapter);
}
else {
adapter.changeCursor(result);
}
}
db.close();
}
}
}
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context=".EventsListActivity" >
<ListView
android:id="@+id/list"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:cacheColorHint="#000000"
android:divider="@color/divider_color"
android:dividerHeight="1dp"
tools:listitem="@layout/event_list_item" >
</ListView>
</RelativeLayout>
Step 1 - From Activity to FragmetActivity
Replace this line:public class MainActivity extends SherlockActivity implements OnItemClickListenerWith this line:
public class MainActivity extends SherlockFragmentActivity implements LecturesListFragment.callback{Which also means that the familiar OnItemClickListener is replaced with the following code:
////////////////////////////
// Fragment interface
///////////////////////////
@Override
public void onLectureClicked(long lectureId) {
if (mTwoPanes) {
SingleLectureFragment f = new SingleLectureFragment();
Bundle b = new Bundle();
b.putLong(SingleLectureFragment.LECTURE_ID, lectureId);
f.setArguments(b);
getSupportFragmentManager().beginTransaction().replace(R.id.lecture_details_container, f).commit();
}
else {
Intent i = new Intent(this,SingleLectureActivity.class);
i.putExtra(SingleLectureActivity.EXTRA_LECTURE_ID, lectureId);
startActivity(i);
}
}
@Override
public void fetchLecturesFromServer() {
Intent i = new Intent(this,CommunicationService.class);
startService(i);
mProgressDialog = ProgressDialog.show(this, getString(R.string.progress_dialog_starting_title), getString(R.string.progress_dialog_starting_message));
}
That implicit freedom is the basis to why i like the concept of Fragments so much.
Step 2 - Creating the Fragment.
The aforementioned LectureListFragment looks like this:
public class LecturesListFragment extends SherlockFragment implements OnItemClickListener{
private ListView mList;
private View mRootView;
private BroadcastReceiver mUpdateReceiver;
private callbacks mListener;
public interface callbacks {
public void onLectureClicked(long lectureId);
public void fetchLecturesFromServer();
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mUpdateReceiver = new BroadcastReceiver() {
//TODO: [Ran] handle network failure
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equalsIgnoreCase(CommunicationService.RESULTS_ARE_IN)) {
reloadLecturesFromDb();
}
}
};
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
mRootView = inflater.inflate(R.layout.single_list_layout, null);
mList = (ListView) mRootView.findViewById(R.id.list);
mList.setOnItemClickListener(this);
reloadLecturesFromDb();
return mRootView;
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
if (!(activity instanceof callbacks)) {
throw new IllegalStateException("Activity must implement callback interface in order to use this fragment");
}
mListener = (callbacks) activity;
}
@Override
public void onPause() {
super.onPause();
if (null != mUpdateReceiver) {
getActivity().unregisterReceiver(mUpdateReceiver);
}
}
@Override
public void onResume() {
super.onResume();
if (null != mUpdateReceiver) {
IntentFilter filter = new IntentFilter();
filter.addAction(CommunicationService.RESULTS_ARE_IN);
getActivity().registerReceiver(mUpdateReceiver, filter);
}
}
@Override
public void onItemClick(AdapterView list, View view, int position, long id) {
if (null != mListener) {
mListener.onLectureClicked(id);
}
}
public void reloadLecturesFromDb() {
new lecturesLoader().execute((Void) null);
}
////////////////////////////////
// Async task that queries the DB in background
////////////////////////////////
private class lecturesLoader extends AsyncTask {
private SQLiteDatabase db;
@Override
protected Cursor doInBackground(Void... params) {
db = new DatabaseHelper(getActivity().getApplicationContext(), DatabaseHelper.DB_NAME,null , DatabaseHelper.DB_VERSION).getReadableDatabase();
return DBUtils.getAllLectures(db);
}
@Override
protected void onPostExecute(Cursor result) {
if (0 == result.getCount()) {
//we don't have anything in our DB, force network refresh
if (null != mListener) {
mListener.fetchLecturesFromServer();
}
}
else {
LecturesAdapter adapter = (LecturesAdapter) mList.getAdapter();
if (null == adapter) {
adapter = new LecturesAdapter(getActivity().getApplicationContext(), result);
mList.setAdapter(adapter);
}
else {
adapter.changeCursor(result);
}
}
db.close();
}
}
}
The Fragment's layout looks like this (suspiciously close to the old main_activity.xml):
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context=".EventsListActivity" >
<ListView
android:id="@+id/list"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:cacheColorHint="#000000"
android:divider="@color/divider_color"
android:dividerHeight="1dp"
tools:listitem="@layout/event_list_item" >
</ListView>
</RelativeLayout>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:background="@color/LecturesListBackground"
tools:context=".EventsListActivity" >
<fragment
android:name="com.gdg.andconlab.ui.LecturesListFragment"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:id="@+id/lectures_fragment"
/>
</RelativeLayout>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal" >
<fragment
android:id="@+id/lectures_fragment"
android:name="com.gdg.andconlab.ui.LecturesListFragment"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_weight="3" />
<LinearLayout
android:id="@+id/lecture_details_container"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_weight="2"
>
</LinearLayout>
</LinearLayout>
* This post is also available in Hebrew here: http://iandroid.co.il/dr-iandroid/archives/15644
Royi is a Google Developer Expert for Android in 2013, a mentor at Google's CampusTLV for Android and (last but not least) the set top box team leader at Vidmind, an OTT TV and Video Platform Provider. www.vidmind.com
No comments:
Post a Comment