Thursday, October 24, 2013

Add Items To ListView Dynamically: Android Tutorial

It can be difficult setting up a ListView in Android using the very basic tools available. It might take awhile getting your head around how the different basic adapters work, so often people just create custom adapters to get things how they want them to be. It's a brute-force method and takes more time than is necessary; all one needs to know is how to use ListActivity and ArrayAdapter to make things shorter and sweeter.

So, let's create an item list which allows us to add text items to it as well as remove them. I'll call the application project "DynamicLV". The main activity will be called "MainActivity" and the layout for it will be called "activity_main." I've set "onClick" in the xml layout file...

activity_main.xml
<?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="vertical" >
    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:gravity="center">
        <Button
            android:id="@+id/addItem"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Add Item"
            android:onClick="onClick"/>
        <Button
            android:id="@+id/deleteItem"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Remove Item"
            android:onClick="onClick"/>
    </LinearLayout>
        <EditText
            android:id="@+id/editText"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:hint="Enter New Item"/>"
        <ListView
              android:id="@android:id/list"
              android:layout_width="match_parent"
              android:layout_height="wrap_content" >
        </ListView>

</LinearLayout>
 Pay attention to the ListView id; this is how we name it when using ListActivity (See MainActivity below)

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

import android.os.Bundle;
import android.app.Activity;
import android.app.ListActivity;
import android.view.Menu;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.EditText;

public class MainActivity extends ListActivity {
                   EditText et;
                  
                   @Override
                   protected void onCreate(Bundle savedInstanceState) {
                       super.onCreate(savedInstanceState);
                       setContentView(R.layout.activity_main);
                       et = (EditText) findViewById(R.id.editText);
                                
                                // use List and not String[]
                       List values = new ArrayList();
                                
                                // simple_list_item_1 is a default row-item layout for ListAdapter     
                       ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
                                              android.R.layout.simple_list_item_1, values);
                       setListAdapter(adapter);
                   }
                  
                   public void onClick(View view) {
                           // we're going to add directly to the adapter, so declare it within the method
                           ArrayAdapter<String> adapter = (ArrayAdapter<String>) getListAdapter();
                           String item;
                           //as with the adapter, we're going to add to the List, so declare it
                           List myList = new ArrayList();
                           switch (view.getId()) {
                           case R.id.addItem:
                                     item = et.getText().toString();
                                     myList.add(item);
                                     adapter.add(item);
                                     et.setText("");
                                     break;
                           case R.id.deleteItem:
                                     if (getListAdapter().getCount() > 0) {
                                         // remove the last item added to the list
                                         item = (String) getListAdapter().getItem(getListAdapter().getCount()-1);
                                         myList.remove(item);
                                         adapter.remove(item);
                                     }
                                     break;       
                           }
                           adapter.notifyDataSetChanged();
                   }

                   @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;
                   }

}

So, now if we run our application, we have an empty list waiting to be edited...

Now, we add 5 items...

And, finally we remove the last item because, well... it's rubbish, isn't it?...


Using ListActivity and ListAdapter is pretty easy once you get your head around it. As always, thanks for following, and let me know if you have any questions or concerns about this tutorial. Cheers!

NOTE: Don't forget to read up on the relevant API...


Wednesday, October 23, 2013

Using SQLiteDatabase To Preserve Data In A ListView: Android

If you haven't yet, Go to Part 1 of this two-part tutorial

In the previous post, we created a basic application that gives us a list showing the order in which the SW movies were released. We created a custom adapter that allowed us to insert object data into each row of the list using a custom row layout. What we still need to do, however, is find a way to save the data so that when we close the application and restart it, the data will persist.

As it stands now, however, we have no way to show that our application isn't actually saving the data because it gets automatically displayed each time we start it up. Just look at the onCreate method in ListFiller.java to see why; we need to add a feature that allows us to control when the movie list gets displayed. This is easily done with a button, so let's add one. Also, let's add an 'exit' button as well.

activity_list_filler.xml
<?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="vertical" >
   
    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:orientation="horizontal">
        <Button
            android:id="@+id/showList"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/showList"
            android:onClick="onClick"/>
        <Button
            android:id="@+id/exit"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/exit"
            android:onClick="onClick"/>
    </LinearLayout>

    <ListView
        android:id="@+id/theList"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:paddingBottom="10sp" />

</LinearLayout>

Graphical Layout

So, above we have our new activity_list_filler.xml file and it's associated Graphical Layout. Now, let's add some code to ListFiller.java to get the buttons working.

ListFiller.java
import java.util.ArrayList;
import java.util.List;
import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;
import android.view.View;
import android.widget.ListView;

public class ListFiller extends Activity {
         ListView movieList;
                  

        @Override
         protected void onCreate(Bundle savedInstanceState) {
                       super.onCreate(savedInstanceState);
                       setContentView(R.layout.activity_list_filler);
                                    
                       List myList = new ArrayList();
                       movieList = (ListView) findViewById(R.id.theList);
                       movieList.setAdapter(new MovieListAdapter(this, R.layout.row_layout, myList));
         }
                  
         public void onClick(View view) {
                @SuppressWarnings("unchecked")
                 ListView movieList = (ListView) findViewById(R.id.theList);
                 MovieListAdapter swadapter = (MovieListAdapter) movieList.getAdapter();
                 SWMovie swmovie = null;
                 switch (view.getId()) {
                 case R.id.showList:
                        List myList = new ArrayList();
                        for (int i = 1; i < 4; i++) {
                            String g = Integer.toString(i);
                            int k = i + 3;
                            String j = Integer.toString(k);
                            myList.add(new SWMovie("Movie " + g, "Episode " + k));
                            swmovie = new SWMovie("Movie " + g, "Episode " + k);
                            swadapter.add(swmovie);
                        }
                        for (int i = 1; i < 4; i++) {
                            String g = Integer.toString(i);
                            int k = i + 3;
                            String j = Integer.toString(k);
                            myList.add(new SWMovie("Movie " + k, "Episode " + g));
                            swmovie = new SWMovie("Movie " + k, "Episode " + g);
                            swadapter.add(swmovie);
                        }
                                                       
                    break;
              case R.id.exit:
                     finish();
                     break;
              }
              swadapter.notifyDataSetChanged();
       }

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

}

So, if you run the application now, you see the following screen upon start-up...

Start-up screen

Click "Show Movie List" and you see...

Movie List screen

Now, click "Exit Application" and restart the application once it closes. You should once again see the start-up screen with no list. This is because we haven't saved the list. We'll do that with SQLite now...

Actually, before we begin, if you'd like to learn more about SQLite now, check out the following links:


We need to create a class that creates the database, so let's do that and call it MovieSQLiteHelper and make it extend SQLiteOpenHelper.

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

public class MovieSQLiteHelper extends SQLiteOpenHelper {
                   // name the Table
                   public static final String TABLE_MOVIES = "movies";
                   // name the Columns of the Table
                   public static final String COLUMN_ID = "_id";
                   public static final String COLUMN_MOVIE = "movie";
                   public static final String COLUMN_EPISODE = "episode";

                   private static final String DATABASE_NAME = "movies.db";
                   private static final int DATABASE_VERSION = 1;
                  
                   // Database creation declaration
                   private static final String DATABASE_CREATE = "create table "
                                           + TABLE_MOVIES + "(" + COLUMN_ID
                                           + " integer primary key autoincrement, " + COLUMN_MOVIE
                                           + " text not null, " + COLUMN_EPISODE + " text not null" + ")";
                  
                   // our object constructor
                   public MovieSQLiteHelper(Context context) {
                                     super(context, DATABASE_NAME, null, DATABASE_VERSION);
                   }
                    
                    
                   @Override
                   public void onCreate(SQLiteDatabase db) {
                                     db.execSQL(DATABASE_CREATE);

                   }

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

                   }

}

Again, if the above looks totally alien to you, or if you're having a hard time following the code, I encourage you to check out the links above.

Now, we're going to create a class which acts as a Data Access Object (DAO). The DAO is a database handler that manages the data and converts objects contained within the database into Java Objects. We'll call the class MoviesDataSource...

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

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

public class MoviesDataSource {
                  
       private SQLiteDatabase database;
       private MovieSQLiteHelper dbHelper;
       private String[] allColumns = { MovieSQLiteHelper.COLUMN_ID,
                                                        MovieSQLiteHelper.COLUMN_MOVIE,     MovieSQLiteHelper.COLUMN_EPISODE };
                  
      public MoviesDataSource(Context context) {
              dbHelper = new MovieSQLiteHelper(context);
     }

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

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

   public SWMovie createSWMovie(String movie, String episode) {
            ContentValues values = new ContentValues();
                      
          values.put(MovieSQLiteHelper.COLUMN_MOVIE, movie);
          values.put(MovieSQLiteHelper.COLUMN_EPISODE, episode);
          long insertId = database.insert(MovieSQLiteHelper.TABLE_MOVIES, null, values);
          Cursor cursor = database.query(MovieSQLiteHelper.TABLE_MOVIES,
                       allColumns, MovieSQLiteHelper.COLUMN_ID + " = " + insertId, null,
                        null, null, null);
         cursor.moveToFirst();
         SWMovie newSWMovie = cursorToSWMovie(cursor);
         cursor.close();
         return newSWMovie;
  }

  public void deleteSWMovie(SWMovie swmovie) {
           long id = swmovie.getId();
           System.out.println("SWMovie deleted with id: " + id);
           database.delete(MovieSQLiteHelper.TABLE_MOVIES,   MovieSQLiteHelper.COLUMN_ID
                           + " = " + id, null);
  }

  public List<SWMovie> getAllSWMovie() {
           List<SWMovie> swmovies = new ArrayList<SWMovie>();

          Cursor cursor = database.query(MovieSQLiteHelper.TABLE_MOVIES,
                       allColumns, null, null, null, null, null);
                      
         cursor.moveToFirst();
         while (!cursor.isAfterLast()) {
            SWMovie swmovie = cursorToSWMovie(cursor);
            swmovies.add(swmovie);
            cursor.moveToNext();
         }
                      
         cursor.close();
         return swmovies;
  }

  private SWMovie cursorToSWMovie(Cursor cursor) {
            SWMovie swmovie = new SWMovie();
            swmovie.setId(cursor.getLong(0));
            swmovie.setMovie(cursor.getString(1));
            swmovie.setEpisode(cursor.getString(2));
            return swmovie;
  }

}

I threw in some code so that movies can be deleted from the list, but I'll leave it as an extra exercise for you to work a 'delete movie' button into the application.

Now, we need to update the main activity to make sure the data gets saved...

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

import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;
import android.view.View;
import android.widget.ListView;

public class ListFiller extends Activity {
        ListView movieList;
        private MoviesDataSource datasource;
                  

       @Override
        protected void onCreate(Bundle savedInstanceState) {
                      super.onCreate(savedInstanceState);
                      setContentView(R.layout.activity_list_filler);
                                    
                      datasource = new MoviesDataSource(this);
                      datasource.open();
                                    
                      List myList = datasource.getAllSWMovie();
                      movieList = (ListView) findViewById(R.id.theList);
                      movieList.setAdapter(new MovieListAdapter(this, R.layout.row_layout, myList));
        }
                  
        public void onClick(View view) {
              @SuppressWarnings("unchecked")
               ListView movieList = (ListView) findViewById(R.id.theList);
               MovieListAdapter swadapter = (MovieListAdapter) movieList.getAdapter();
               SWMovie swmovie = null;
               switch (view.getId()) {
               case R.id.showList:
                     List myList = new ArrayList();
                     for (int i = 1; i < 4; i++) {
                        String g = Integer.toString(i);
                        int k = i + 3;
                        String j = Integer.toString(k);
                        myList.add(new SWMovie("Movie " + g, "Episode " + k));
                        swmovie = new SWMovie("Movie " + g, "Episode " + k);
                        swmovie = datasource.createSWMovie(swmovie.getMovie(),     
                                        swmovie.getEpisode());
                        swadapter.add(swmovie);
                    }
                    for (int i = 1; i < 4; i++) {
                        String g = Integer.toString(i);
                        int k = i + 3;
                        String j = Integer.toString(k);
                        myList.add(new SWMovie("Movie " + k, "Episode " + g));
                        swmovie = new SWMovie("Movie " + k, "Episode " + g);
                        swmovie = datasource.createSWMovie(swmovie.getMovie(),    
                                        swmovie.getEpisode());
                        swadapter.add(swmovie);
                    }
                                                       
                    break;
                    case R.id.exit:
                         finish();
                         break;
                    }
                       swadapter.notifyDataSetChanged();
                    }
                  
                   @Override
                    protected void onResume() {
                                 datasource.open();
                                 super.onResume();
                  }
                    
                 @Override
                  protected void onPause() {
                               datasource.close();
                               super.onPause();
                 }

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

}

Make sure to read through all the code in ListFiller.java and understand how the database is made to store and display the data. Run the application, click "Show Movie List", click "Exit Application" after the list is displayed, and once the application is closed, restart the application. The movie list should display upon opening, which means the data has been correctly stored in the database.

If you have any questions or concerns about this tutorial, please drop a comment below. Thanks for following!