oAuth2 with Android (Part 1)

This is the third post in the series over oAuth with Symfony2, iOS and Android. All posts in this serie can be found here. While the previous post about symfony2 was one large post, I am going to try splitting this in a few posts.

All code from this post can be found here at Github. Because wordpress doesn’t seem to work that well with the included code in this blog, I suggest to use the code from the repository. I created a tag in the repo for the part that was at the end of this first part.

With android, there are several possibilities to do the actual development. While Eclipse is (Still) the preferred IDE, I actually like Android Studio more (And I am pretty sure Android Studio will soon fully replace Eclipse with their ADT).
The only disadvantage sometimes are the many releases of Android Studio, with sometimes features that break stuff in your project. The current version from Android Studio that I will be using for this project is 0.5.8, with tools version 22.6.
Testing the app will be done on a Nexus 7 (First version) with Android 4.4. I won’t be using the emulator.

The first step is to  create a new project for the app in Android Studio. I am using the next settings for the project:Project settings
In a normal situation you wouldn’t choose the min-sdk as 4.4, but for this app it doesn’t really matter. If the device you are using doesn’t have Android 4.4, you can always choose a lower Android version, this app doesn’t use kitkat specific APIs. On the next page I choose for a “blank activity with fragment” , and name it “activity_main” (The default).
After the creating the project, Android Studio takes a few minutes to setup up all dependencies.
Now we have a very simple app, with basically nothing. However, it should work on a Android device, and should looks like the image left. First view of the app Before we start with adding support for oAuth2, we are going to create a list with current Demo’s first. For this, we are going to use a very simple list view.
The design of the app is made in the main_fragment.xml, so open that in Android Studio. By default, there is a simple TextView with “Hello World”  as text. We don’t want to say hello to the world, so we can simply remove that. Now, lets add a list view. This can be done by dragging the list view in design modes from the left into the device. It should look like the screenshot here left.Example view for the list view Now we have a list view, we just need some code to display data in the list view. This will be done in activity_main.java with the following code in the onCreateView for the fragment (Replace the current code in the onCreateView, the first and last line from the code below should be the only code currently in the onCreateView.):

            View rootView = inflater.inflate(R.layout.fragment_main, container, false);

            ArrayList data = new ArrayList();

            data.add("Test");
            data.add("Test2");
            data.add("test3");

            ArrayAdapter adapter = new ArrayAdapter(getActivity(), android.R.layout.simple_list_item_1, data);

            ListView dataList = (ListView) rootView.findViewById(R.id.listView);
            dataList.setAdapter(adapter);

            return rootView;

The app should now looks like the image left. Dynamic listviewWhile you can do all kind of fancy things with list views, I decided to keep it simple, so there will be only a title shown in the list, and nothing else. With fairly easy changes things like the description, but also something like a image, can be added to the list view, however thats not the point of this app.
While what we now have is a very nice list view, it is not something what we want really. We want to have a “internal” database with all Demo’s that exists on the server. To accomplish this, we are going to use a SQLite database in the background, and create the list based on data from that database.
We are going to do the network related stuff (That will follow in the next part of this example) in separate threads, while updating the User Interface needs to be done in the UIThread. To make sure our network calls will still work later on, we will use a handler to do our Database updates. The code here is based off the this tutorial by Lars Vogel.

DBHelper.java:

package sohier.me.saiod.android;

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;

public class DBHelper extends SQLiteOpenHelper {

    public static final String TABLE_DEMO = "demo";
    public static final String DEMO_ID = "_id";
    public static final String DEMO_TITLE = "title";
    public static final String DEMO_DESCRIPTION = "description";

    private static final String DATABASE_NAME = "demo.db";
    private static final int DATABASE_VERSION = 1;

    // Database creation sql statement
    private static final String DATABASE_CREATE = "create table "
            + TABLE_DEMO + "(" + DEMO_ID
            + " integer primary key autoincrement, " + DEMO_TITLE
            + " text not null, " + DEMO_DESCRIPTION + " text not null);";

    public DBHelper(Context context) {
        super(context, DATABASE_NAME, null, DATABASE_VERSION);
    }

    @Override
    public void onCreate(SQLiteDatabase database) {
        database.execSQL(DATABASE_CREATE);
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        Log.w(DBHelper.class.getName(),
                "Upgrading database from version " + oldVersion + " to "
                        + newVersion + ", which will destroy all old data"
        );
        db.execSQL("DROP TABLE IF EXISTS " + TABLE_DEMO);
        onCreate(db);
    }

}

The demo object:
Demo.java:

package sohier.me.saiod.android;

public class Demo {

    private String title;
    private String description;
    private long id;

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public String toString()
    {
        return this.title;
    }
}

And the DOA for the Demo:
DemoDataSource.java:

package sohier.me.saiod.android;

import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;

import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

public class DemoDataSource {

    // Database fields
    private SQLiteDatabase database;
    private DBHelper dbHelper;
    private String[] allColumns = { DBHelper.DEMO_ID, DBHelper.DEMO_TITLE, DBHelper.DEMO_TITLE };

    public DemoDataSource(Context context) {
        dbHelper = new DBHelper(context);
    }

    public void open() throws SQLException {
        database = dbHelper.getWritableDatabase();
    }

    public void close() {
        dbHelper.close();
    }

    public Demo createDemo(String title, String description) {
        ContentValues values = new ContentValues();
        values.put(DBHelper.DEMO_TITLE, title);
        values.put(DBHelper.DEMO_DESCRIPTION, description);

        long insertId = database.insert(DBHelper.TABLE_DEMO, null,
                values);
        Cursor cursor = database.query(DBHelper.TABLE_DEMO,
                allColumns, DBHelper.DEMO_ID + " = " + insertId, null,
                null, null, null);
        cursor.moveToFirst();
        Demo newDemo = cursorToDemo(cursor);
        cursor.close();
        return newDemo;
    }

    public void deleteDemo(Demo demo) {
        long id = demo.getId();
        System.out.println("Demo deleted with id: " + id);
        database.delete(DBHelper.TABLE_DEMO, DBHelper.DEMO_ID
                + " = " + id, null);
    }

    public List getAllDemos() {
        List demos = new ArrayList();

        Cursor cursor = database.query(DBHelper.TABLE_DEMO,
                allColumns, null, null, null, null, null);

        cursor.moveToFirst();
        while (!cursor.isAfterLast()) {
            Demo demo = cursorToDemo(cursor);
            demos.add(demo);
            cursor.moveToNext();
        }
        // make sure to close the cursor
        cursor.close();
        return demos;
    }

    private Demo cursorToDemo(Cursor cursor) {
        Demo demo = new Demo();
        demo.setId(cursor.getLong(0));
        demo.setTitle(cursor.getString(1));
        demo.setDescription(cursor.getString(2));
        return demo;
    }
}

Now we need to update the fragment in the mainActivity with a handler, this will be called later from separate threads, and the database calls. New fragment class:

    public static class PlaceholderFragment extends Fragment {

        public PlaceholderFragment() {
        }

        ArrayAdapter adapter;
        Handler handler = new Handler() {};

        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container,
                Bundle savedInstanceState) {
            View rootView = inflater.inflate(R.layout.fragment_main, container, false);

            ArrayList data = new ArrayList();

            DemoDataSource datasource = new DemoDataSource(this.getActivity());
            datasource.open();

            List values = datasource.getAllDemos();

            adapter = new ArrayAdapter(getActivity(), android.R.layout.simple_list_item_1, values);

            ListView dataList = (ListView) rootView.findViewById(R.id.listView);
            dataList.setAdapter(adapter);

            return rootView;
        }
    }

Because we select everything from our database now, there won’t be any data again in our list. But thats for the next part, getting the data from the simple symfony2 site. Before we are able to do that, we will need to connect with oAuth to the site.

Before getting to the next part, we are going to create a very simple layout of adding a new Demo. When we created the Project, Android Studio added a menu/main.xml, with 1 item. We are going to replace this item with a button to add a new Demo.
New main.xml:


 

You will also need to add the string to strings.xml (Instead of action_settings). Within our main_activity we will have the ability to do something when the button is pressed. Before we are going to create that code, we need to decide how to display the screen that adds the Demo. For this example, I decided to use a DialogFragment with a custom layout. This layout has two simple EditTexts for title and Description. Besides that, it has two buttons with cancel and create, however these will not be in the layout, but created from the code. Layout for the dialog: dialog_add_demo.xml


Now lets use this layout, by creating a new DialogFragment subclass. We create this as interclass in the main_activity:

    public class CreateDemoDialog extends DialogFragment {
        @Override
        public Dialog onCreateDialog(Bundle savedInstanceState) {
            AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
            // Get the layout inflater
            LayoutInflater inflater = getActivity().getLayoutInflater();

            // Inflate and set the layout for the dialog
            // Pass null as the parent view because its going in the dialog layout
            builder.setView(inflater.inflate(R.layout.dialog_add_demo, null))
                    // Add action buttons
                    .setPositiveButton(R.string.create, new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int id) {
                            // sign in the user ...
                        }
                    })
                    .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
                        public void onClick(DialogInterface dialog, int id) {
                            CreateDemoDialog.this.getDialog().cancel();
                        }
                    });
            return builder.create();
        }
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();
        if (id == R.id.action_add) {
            CreateDemoDialog cdd = new CreateDemoDialog();
            cdd.show(getFragmentManager(), "");
            return true;
        }
        return super.onOptionsItemSelected(item);
    }

This code creates a new instance of the CreateDemoDialog we created, and show it over the current app. On the left you can see how it should look like now.Dialog for creating a new DemoNote that the create button doesn’t actually do anything yet, as we didn’t add any code for in their. There is also no option to delete anything in this code.
Creating is fairly straight forward, without network code. We just create datasource.createDemo with the title and description. Deleting is just calling datasource.deleteDemo. In the code below, both are added to the final code. The use of the handler is here not yet needed, as we are already in the UIThread. With this done we have now a “fully” functional app, but without any network code yet, that will be the subject of the next post.

package sohier.me.saiod.android;

import android.app.Activity;
import android.app.ActionBar;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.DialogFragment;
import android.app.Fragment;
import android.content.DialogInterface;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.os.Build;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.EditText;
import android.widget.ListView;

import java.util.ArrayList;
import java.util.List;


public class MainActivity extends Activity {

    private static PlaceholderFragment fragment;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        if (savedInstanceState == null) {
            fragment = new PlaceholderFragment();
            getFragmentManager().beginTransaction()
                    .add(R.id.container, fragment)
                    .commit();
        }
    }


    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();
        if (id == R.id.action_add) {
            CreateDemoDialog cdd = new CreateDemoDialog();
            cdd.show(getFragmentManager(), "");
            return true;
        }
        return super.onOptionsItemSelected(item);
    }



    /**
     * A placeholder fragment containing a simple view.
     */
    public static class PlaceholderFragment extends Fragment {

        public PlaceholderFragment() {
        }

        public ArrayAdapter adapter;
        public Handler handler = new Handler() {};
        public DemoDataSource datasource;

        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container,
                Bundle savedInstanceState) {
            View rootView = inflater.inflate(R.layout.fragment_main, container, false);

            ArrayList data = new ArrayList();

            datasource = new DemoDataSource(this.getActivity());
            datasource.open();

            List values = datasource.getAllDemos();

            adapter = new ArrayAdapter(getActivity(), android.R.layout.simple_list_item_1, values);

            ListView dataList = (ListView) rootView.findViewById(R.id.listView);
            dataList.setAdapter(adapter);

            dataList.setLongClickable(true);

            dataList.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {

                public boolean onItemLongClick(AdapterView< ?> arg0, View arg1,
                                               int pos, long id) {
                    Log.v("long clicked","pos: " + pos);

                    datasource.deleteDemo(adapter.getItem(pos));
                    adapter.remove(adapter.getItem(pos));
                    adapter.notifyDataSetChanged();

                    return true;
                }
            });

            return rootView;
        }
    }
    public static class CreateDemoDialog extends DialogFragment {
        @Override
        public Dialog onCreateDialog(Bundle savedInstanceState) {
            AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
            // Get the layout inflater
            LayoutInflater inflater = getActivity().getLayoutInflater();

            // Inflate and set the layout for the dialog
            // Pass null as the parent view because its going in the dialog layout
            View vw = inflater.inflate(R.layout.dialog_add_demo, null);
            final EditText title = (EditText)vw.findViewById(R.id.title);
            final EditText desc = (EditText)vw.findViewById(R.id.description);

            builder.setView(vw)
                    // Add action buttons
                    .setPositiveButton(R.string.create, new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int id) {


                            final Demo d = fragment.datasource.createDemo(title.getText().toString(), desc.getText().toString());
                            Runnable r = new Runnable() {
                                @Override
                                public void run() {
                                    fragment.adapter.add(d);
                                    fragment.adapter.notifyDataSetChanged();
                                    Log.d("saiod", "changed.");
                                }
                            };
                            fragment.handler.post(r);
                        }
                    })
                    .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
                        public void onClick(DialogInterface dialog, int id) {
                            CreateDemoDialog.this.getDialog().cancel();
                        }
                    });
            return builder.create();
        }
    }
}

Leave a Reply

Your email address will not be published. Required fields are marked *