Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Stuck with porting from activity to fragment

It's now one week exactly that I try to port a simple activity-based app to fragments. I'm totally stuck.

This beast is a simple List, Details, Add/Edit app with contextmenu and optionmenus. I tried to make it right: Fragments and activities each in their own file, using the v4 support-package for phone and tablet, fragments do everything a re-usable fragment should do and callbacks (lots of them) fly around to inform activities and fragments about what to do. Converting from SQLiteOpenHelper to ContentProvider, converting from optionmenu to actionbarmenu, and, and, and, ... (nearly everything I used is deprecated now).

It's horrible. My simple and small working activity-based app has nearly 3 times the size now and lots of things are not working yet.

If it's required I can add my code here - but it's a lot of stuff (you've been warned).

My question: Is there somebody willing to share a complete example with List, Details AND Add/Edit? This example should use seperate files for Fragments and Activities (not that all-in-one package from Google).

Please don't down-vote. I really would like to see how to make it right.

Many thanks in advance.

EDIT:

Here's the starting activity with it's two layouts (res/layout for phone and res/layout-large-land for tablets) and the contextmenu:

public class ActivityList extends FragmentActivity implements FragmentList.MyContextItemSelectedListener,
                                                      FragmentList.MyDeleteListener,
                                                      FragmentList.MyListItemClickListener,
                                                      FragmentList.MyOptionsItemSelectedListener,
                                                      FragmentDetails.MyDeleteListener,
                                                      FragmentDetails.MyOptionsItemSelectedListener {

    @Override
    public void myContextItemSelected(final int action, final long id) {
        if (action == R.id.men_add) {
            processEdit(0);
        } else if (action == R.id.men_delete) {
            processUpdateList();
        } else if (action == R.id.men_details) {
            processDetails(id);
        } else if (action == R.id.men_edit) {
            processEdit(id);
        }
    }

    @Override
    public void myDelete(final long id) {
        processUpdateList();
    }

    @Override
    public void myListItemClick(final long id) {
        processDetails(id);
    }

    @Override
    public void myOptionsItemSelected(final int action) {
        myOptionsItemSelected(action, 0);
    }

    @Override
    public void myOptionsItemSelected(final int action, final long id) {
        if (action == R.id.men_add) {
            processEdit(0);
        } else if (action == R.id.men_edit) {
            processEdit(id);
        } else if (action == R.id.men_preferences) {
            processPreferences();
        }
    }

    @Override
    protected void onActivityResult(final int requestCode, final int resultCode, final Intent intent) {
        processUpdateList();
    }

    @Override
    public void onCreate(final Bundle bundle) {
        super.onCreate(bundle);

        setContentView(R.layout.activitylist);
    }

    private void processEdit(final long id) {
        Intent intent = new Intent(this, ActivityEdit.class);
        intent.putExtra("ID", id);
        startActivityForResult(intent, MyConstants.DLG_TABLE1EDIT);
    }

    private void processDetails(final long id) {
        if (Tools.isXlargeLand(getApplicationContext())) {
            Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.right);
            if (fragment == null ||
                    (fragment instanceof FragmentDetails && ((FragmentDetails) fragment).getCurrentId() != id)) {
                fragment = new FragmentDetails(id);

                FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
                transaction.replace(R.id.right, fragment);
                transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
                transaction.commit();
            }
        } else {
            Intent intent = new Intent(this, ActivityDetails.class);
            intent.putExtra("ID", id);
            startActivityForResult(intent, MyConstants.DLG_TABLE1SHOW);
        }
    }

    private void processPreferences() {
        Intent intent = new Intent(this, MyPreferenceActivity.class);
        startActivityForResult(intent, MyConstants.DLG_PREFERENCES);
    }

    private void processUpdateList() {
        // TODO:
    }
}

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal" >

    <fragment 
        class="com.test.app.FragmentList"
        android:id="@+id/fragmentlist"
        android:layout_height="match_parent"
        android:layout_width="match_parent"
        android:name="com.test.app.FragmentList" />
</LinearLayout>

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal" >

    <fragment 
        class="com.test.app.FragmentList"
        android:id="@+id/fragmentlist"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:layout_width="0dip"
        android:name="com.test.app.FragmentList" />

    <FrameLayout
        android:id="@+id/right"
        android:layout_height="match_parent"
        android:layout_weight="2"
        android:layout_width="0dip" />
</LinearLayout>

Here's the ListFragment with it's row layout, optionsmenu and contextmenu:

public class FragmentList extends ListFragment implements LoaderManager.LoaderCallbacks<Cursor> {

    private SimpleCursorAdapter           adapter;
    private AlertDialog                   alertDialog;
    private Context                       context;
    private MyContextItemSelectedListener contextItemSelectedListener;
    private MyDeleteListener              deleteListener;
    private long                          id;
    private MyListItemClickListener       listItemClickListener;
    private ListView                      listView;
    private MyOptionsItemSelectedListener optionsItemSelectedListener;

    public interface MyContextItemSelectedListener {
        public void myContextItemSelected(int action, long id);
    }

    public interface MyDeleteListener {
        public void myDelete(long id);
    }

    public interface MyListItemClickListener {
        public void myListItemClick(long id);
    }

    public interface MyOptionsItemSelectedListener {
        public void myOptionsItemSelected(int action);
    }

    @Override
    public void onActivityCreated(final Bundle bundle) {
        super.onActivityCreated(bundle);

        context = getActivity().getApplicationContext();

        listView = getListView();

        getActivity().getSupportLoaderManager().initLoader(MyConstants.LDR_TABLE1LIST, null, this);

        adapter = new SimpleCursorAdapter(context,
                                          R.layout.fragmentlist_row,
                                          null,
                                          new String[] { Table1.DESCRIPTION },
                                          new int[] { R.id.fragmentlist_row_description },
                                          CursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER);
        setListAdapter(adapter);
        setListShown(false);

        registerForContextMenu(listView);

        if (bundle != null && bundle.containsKey("ID")) {
            id = bundle.getLong("ID");
            listItemClickListener.myListItemClick(id);
        }

        if (Tools.isXlargeLand(context)) {
            listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
        }

        setHasOptionsMenu(true);
    }

    @Override
    public void onAttach(final Activity activity) {
        super.onAttach(activity);

        // Reduced: Check for implemented listeners
    }

    @Override
    public boolean onContextItemSelected(final MenuItem menuItem) {
        AdapterContextMenuInfo adapterContextMenuInfo = (AdapterView.AdapterContextMenuInfo) menuItem.getMenuInfo();

        final long id = adapterContextMenuInfo.id;

        if (menuItem.getItemId() == R.id.men_delete) {
            processAlertDialog(id);
            return true;
        } else {
            contextItemSelectedListener.myContextItemSelected(menuItem.getItemId(), adapterContextMenuInfo.id);
        }

        return super.onContextItemSelected(menuItem);
    }

    @Override
    public void onCreateContextMenu(final ContextMenu contextMenu, final View view, final ContextMenuInfo contextMenuInfo) {
        super.onCreateContextMenu(contextMenu, view, contextMenuInfo);

        if (view.getId() == android.R.id.list) {
            getActivity().getMenuInflater().inflate(R.menu.fragmentlist_context, contextMenu);
        }
    }

    @Override
    public Loader<Cursor> onCreateLoader(final int id, final Bundle bundle) {
        MyCursorLoader loader = null;

        switch (id) {
            case MyConstants.LDR_TABLE1LIST:
                loader = new MyCursorLoader(context,
                                            MySQLiteOpenHelper.TABLE1_FETCH,
                                            null);
                break;
        }

        return loader;
    }

    @Override
    public void onCreateOptionsMenu(final Menu menu, final MenuInflater menuInflater) {
        super.onCreateOptionsMenu(menu, menuInflater);

        menu.clear();

        menuInflater.inflate(R.menu.fragmentlist, menu);
    }

    @Override
    public void onListItemClick(final ListView listView, final View view, final int position, final long id) {
        super.onListItemClick(listView, view, position, id);

        this.id = id;

        if (Tools.isXlargeLand(context)) {
            listView.setItemChecked(position, true);
        }

        listItemClickListener.myListItemClick(id);
    }

    @Override
    public void onLoaderReset(final Loader<Cursor> loader) {
        adapter.swapCursor(null);
    }

    @Override
    public void onLoadFinished(final Loader<Cursor> loader, final Cursor cursor) {
        adapter.swapCursor(cursor);

        setListShown(true);
    }

    @Override
    public boolean onOptionsItemSelected(final MenuItem menuItem) {
        optionsItemSelectedListener.myOptionsItemSelected(menuItem.getItemId());

        return super.onOptionsItemSelected(menuItem);
    }

    @Override
    public void onSaveInstanceState(final Bundle bundle) {
        super.onSaveInstanceState(bundle);

        bundle.putLong("ID", id);
    }

    private void processAlertDialog(final long id) {
        final AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(getActivity());
        alertDialogBuilder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {

            @Override
            public void onClick(final DialogInterface dialogInterface, final int which) {
                dialogInterface.dismiss();
            }
        } );
        alertDialogBuilder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {

            @Override
            public void onClick(final DialogInterface dialogInterface, final int which) {
                MyApplication.getSqliteOpenHelper().deleteTable1(id);

                alertDialog.dismiss();

                deleteListener.myDelete(id);
            }
        } );
        alertDialogBuilder.setCancelable(false);
        alertDialogBuilder.setMessage(R.string.txt_reallydelete);

        alertDialog = alertDialogBuilder.create();
        alertDialog.show();
    }
}

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_height="wrap_content"
    android:layout_width="fill_parent"
    android:orientation="horizontal"
    android:paddingBottom="2dip"
    android:paddingTop="2dip" >

    <TextView
        style="@style/TextViewLarge"
        android:id="@+id/fragmentlist_row_description"
        android:textStyle="bold" />
</LinearLayout>


<menu
    xmlns:android="http://schemas.android.com/apk/res/android">

    <item
        android:icon="@drawable/ic_menu_add"
        android:id="@+id/men_add"
        android:showAsAction="ifRoom|withText"
        android:title="@string/txt_add" />

    <item
        android:icon="@drawable/ic_menu_preferences"
        android:id="@+id/men_preferences"
        android:showAsAction="ifRoom|withText"
        android:title="@string/txt_preferences" />
</menu>

<menu
    xmlns:android="http://schemas.android.com/apk/res/android">

    <item
        android:id="@+id/men_details"
        android:title="@string/txt_details" />

    <item
        android:id="@+id/men_edit"
        android:title="@string/txt_edit" />

    <item
        android:id="@+id/men_delete"
        android:title="@string/txt_delete" />
</menu>

This is DetailsActivity:

public class ActivityDetails extends FragmentActivity implements FragmentDetails.MyDeleteListener, 
                                                                    FragmentDetails.MyOptionsItemSelectedListener {

    private long id;

    @Override
    public void myDelete(final long id) {
        setResult(RESULT_OK);
        finish();
    }

    @Override
    public void myOptionsItemSelected(final int action, final long id) {
        if (action == R.id.men_add) {
            processEdit(0);
        } else if (action == R.id.men_edit) {
            processEdit(id);
        } else if (action == R.id.men_preferences) {
            processPreferences();
        }
    }

    @Override
    protected void onActivityResult(final int requestCode, final int resultCode, final Intent intent) {
        if (requestCode == MyConstants.DLG_PREFERENCES || requestCode == MyConstants.DLG_TABLE1EDIT) {
            finish();

            startActivity(getIntent()); 
        }
    }

    @Override
    protected void onCreate(final Bundle bundle) {
        super.onCreate(bundle);

        if (bundle != null) {
            if (bundle.containsKey("ID")) {
                id = bundle.getLong("ID");
            }
        } else {
            Bundle bundleExtras = getIntent().getExtras();
            if (bundleExtras != null) {
                id = bundleExtras.getLong("ID");
            }

            processDetails(id);
        }
    }

    @Override
    public void onSaveInstanceState(final Bundle bundle) {
        super.onSaveInstanceState(bundle);

        bundle.putLong("ID", id);
    }

    private void processDetails(final long id) {
        FragmentDetails fragment = new FragmentDetails(id);

        FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
        transaction.replace(android.R.id.content, fragment);
        transaction.commit();
    }

    private void processEdit(final long id) {
        Intent intent = new Intent(this, ActivityEdit.class);
        intent.putExtra("ID", id);
        startActivityForResult(intent, MyConstants.DLG_TABLE1EDIT);
    }

    private void processPreferences() {
        Intent intent = new Intent(this, MyPreferenceActivity.class);
        startActivityForResult(intent, MyConstants.DLG_PREFERENCES);
    }
}

Here's the DetailsFragment with Layout and Menu:

public class FragmentDetails extends Fragment {

    private AlertDialog                   alertDialog;
    private MyDeleteListener              deleteListener;
    private long                          id;
    private MyOptionsItemSelectedListener optionsItemSelectedListener;
    private TextView                      textViewDescription;
    private TextView                      textViewId;

    public FragmentDetails() {
        id = 0;
    }

    public FragmentDetails(final long id) {
        this.id = id;
    }

    public long getCurrentId() {
        return id;
    }

    public interface MyDeleteListener {
        public void myDelete(long id);
    }

    public interface MyOptionsItemSelectedListener {
        public void myOptionsItemSelected(int action, long id);
    }

    @Override
    public void onActivityCreated(final Bundle bundle) {
        super.onActivityCreated(bundle);

        if (bundle != null && bundle.containsKey("ID")) {
            id = bundle.getLong("ID");
        }

        setHasOptionsMenu(true);
    }

    @Override
    public void onAttach(final Activity activity) {
        super.onAttach(activity);

        // Reduced
    }

    @Override
    public void onCreateOptionsMenu(final Menu menu, final MenuInflater menuInflater) {
        super.onCreateOptionsMenu(menu, menuInflater);

        menu.clear();

        menuInflater.inflate(R.menu.fragmentdetails, menu);
    }

    @Override
    public View onCreateView(final LayoutInflater inflater, final ViewGroup viewGroup, final Bundle bundle) {
        View view = inflater.inflate(R.layout.fragmentdetails, null);

        textViewDescription = (TextView) view.findViewById(R.id.tv_description);
        textViewId = (TextView) view.findViewById(R.id.tv_id);

        if (id != 0) {
            Table1 table1;
            if ((table1 = MyApplication.getSqliteOpenHelper().getTable1(id)) != null) {
                textViewDescription.setText(Tools.defaultString(table1.getDescription()));
                textViewId.setText(Tools.defaultString(String.valueOf(table1.getId())));
            }
        }

        return view;
    }

    @Override
    public boolean onOptionsItemSelected(final MenuItem menuItem) {
        if (menuItem.getItemId() == R.id.men_delete) {
            processAlertDialog(id);
            return true;
        } else {
            optionsItemSelectedListener.myOptionsItemSelected(menuItem.getItemId(), id);
        }

        return super.onOptionsItemSelected(menuItem);
    }

    @Override
    public void onSaveInstanceState(final Bundle bundle) {
        super.onSaveInstanceState(bundle);

        bundle.putLong("ID", id);
    }

    private void processAlertDialog(final long id) {
        final AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(getActivity());
        alertDialogBuilder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {

            @Override
            public void onClick(final DialogInterface dialogInterface, final int which) {
                alertDialog.dismiss();
                alertDialog = null;
            }
        } );
        alertDialogBuilder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {

            @Override
            public void onClick(final DialogInterface dialogInterface, final int which) {
                MyApplication.getSqliteOpenHelper().deleteTable1(id);

                alertDialog.dismiss();
                alertDialog = null;

                deleteListener.myDelete(id);
            }
        } );
        alertDialogBuilder.setCancelable(false);
        alertDialogBuilder.setMessage(R.string.txt_reallydelete);

        alertDialog = alertDialogBuilder.create();
        alertDialog.show();
    }
}

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_height="wrap_content"
    android:layout_width="match_parent"
    android:orientation="vertical" >

    <LinearLayout
        android:layout_height="wrap_content"
        android:layout_width="match_parent"
        android:orientation="horizontal" >

        <TextView
            style="@style/TextViewStandard"
            android:layout_weight="1" 
            android:text="@string/txt_id" />

        <TextView
            style="@style/TextViewStandard"
            android:id="@+id/tv_id"
            android:layout_weight="1" />
    </LinearLayout>

    <LinearLayout
        android:layout_height="wrap_content"
        android:layout_width="match_parent"
        android:orientation="horizontal" >

        <TextView
            style="@style/TextViewStandard"
            android:layout_weight="1" 
            android:text="@string/txt_description" />

        <TextView
            style="@style/TextViewStandard"
            android:id="@+id/tv_description"
            android:layout_weight="1" />
    </LinearLayout>
</LinearLayout>

<menu
    xmlns:android="http://schemas.android.com/apk/res/android">

    <item
        android:icon="@drawable/ic_menu_add"
        android:id="@+id/men_add"
        android:showAsAction="ifRoom|withText"
        android:title="@string/txt_add" />

    <item
        android:icon="@drawable/ic_menu_edit"
        android:id="@+id/men_edit"
        android:showAsAction="ifRoom|withText"
        android:title="@string/txt_edit" />

    <item
        android:icon="@drawable/ic_menu_delete"
        android:id="@+id/men_delete"
        android:showAsAction="ifRoom|withText"
        android:title="@string/txt_delete" />

    <item
        android:icon="@drawable/ic_menu_preferences"
        android:id="@+id/men_preferences"
        android:showAsAction="ifRoom|withText"
        android:title="@string/txt_preferences" />
</menu>

I don't post the EditActivity because it's simply an FragmentActivity without a Fragment.

like image 592
Harald Wilhelm Avatar asked Apr 04 '12 14:04

Harald Wilhelm


People also ask

Are fragments faster than activity?

I do find fragments a slightly faster than activities but really its not something you would really notice in most cases. Regardless if they was intended for speed or not they still seem/feel little quicker. The downside of using fragments, is certain callbacks like onBackPressed is only in an activity.

Can I use activity instead of fragment?

It can't exist independently. We can't create multi-screen UI without using fragment in an activity, After using multiple fragments in a single activity, we can create a multi-screen UI. Fragment cannot be used without an Activity.

How do you call a fragment in intent?

Fragment needs to be hosted by a FragmentActivity , you can't add a fragment via an Intent . You need to create a FragmentManager to add your fragment in the FragmentActivity (or call another FragmentActivity via an Intent and add your fragment on it).


1 Answers

This might not be the whole answer but part of the answer: You still have a main activity, in your xml where you used to have listview you now add a framelayout. Then in your activities oncreate you add following:

        mMainFragment = new ListFragment();
        FragmentManager fragmentManager = getSupportFragmentManager();
        FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();

        fragmentTransaction.replace(R.id.center_container, mMainFragment);

        fragmentTransaction.commit();
        mCurrentFragment = mMainFragment;

In your listfragment

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
        Bundle savedInstanceState) {
    // setup view
    View view = inflater.inflate(R.layout.calendar_list, null);

    mListAdapter = new CustomAdapter(getActivity(), R.layout.calendar_row, (ArrayList<Item>) mFullList);
    setListAdapter(mListAdapter);

    return view;
}

XML for listfragment:

<somelayout>
    <ListView android:id="@id/android:list"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
</somelayout>

Click on list is triggered in fragment with:

@Override
public void onListItemClick(ListView list, View view, int position, long id) {
    final Item item = (Item) list.getAdapter().getItem(position);
    mListener.OnListClick(item);
}

Which uses this listener:

public interface OnListItemClickListener { public void OnListClick(Item item); }

The Listfragment needs to have this in top:

@Override
public void onAttach(Activity activity) {
    super.onAttach(activity);
    try {
        mListener = (OnListItemClickListener) activity;
    } catch (ClassCastException e) {
        throw new ClassCastException(activity.toString() + " must implement OnListItemClickListener");
    }
}

The main activity then subscribes to this by implementing the interface and start the detail fragment when the listener is triggered.

EDIT: Okay, so your question is far more fundamental :) remember oncreate is called in your activity every time you rotate so your activity needs to remember which fragment to show just like it needs to remember which view to show. Also you need to add fragments to the back stack or the back key wont work with them. Think of fragments as views with function, they are not activities.

like image 162
Warpzit Avatar answered Nov 03 '22 23:11

Warpzit