The Android platform offers a wide range of storage options for use within your apps. In this tutorial series, we are going to explore some of the data storage facilities provided by the Android SDK by building a simple project: an ASCII art editor.
This tutorial series on Creating a Simple ASCII Art Editor is presented in four parts:
Building the User Interface
Image Export & User Configuration
Database Creation & Querying
Saving and Deleting ASCII Pictures
Step 1: Create a Database Helper Class
To manage an SQLite database in Android apps, we extend the SQLiteOpenHelper class. This class will handle database creation, so we will define the data structure within it. Create a new class in your Android project and name it "ImageDataHelper" or any other name of your choice. Extend the class declaration opening line as follows:
1
public class ImageDataHelper extends SQLiteOpenHelper {
Add the following imports at the top of the class:
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.provider.BaseColumns;
Step 2: Define the Database Properties
Inside your database helper class, create the following variables to define the database properties. First, the database version:
1
private static final int DATABASE_VERSION = 1;
Next give the database a name:
1
private static final String DATABASE_NAME = "<span class="skimlinks-unlinked">asciipics.db</span>";
In order to create a reliable database model, we need an ID column, so add one using the BaseColumns constant:
1
public static final String ID_COL = <span class="skimlinks-unlinked">BaseColumns._ID</span>;
This automatically gives us a primary key column that will auto-increment. The database is going to contain a single table, so give it a name next:
1
public static final String TABLE_NAME = "pics";
The table will contain two columns, one for the content of the ASCII artwork, which will be a text string, and one for a name, which will appear in a list when the user attempts to load saved artworks. Define these columns now:
1
2
public static final String ASCII_COL = "ascii_text";
public static final String CREATED_COL = "pic_creation";
Now we can define the database creation string:
private static final String DATABASE_CREATE = "CREATE TABLE " + TABLE_NAME + " (" + ID_COL + " INTEGER " +
"PRIMARY KEY AUTOINCREMENT, " + ASCII_COL + " TEXT, " + CREATED_COL + " TEXT);";
As you can see, the syntax involves standard SQL in this case.
Step 3: Implement Database Creation
We are going to use a Singleton design pattern for the database helper class, which you may not have come across depending on your Java experience. So that we can use the database helper in more than one Activity, while maintaining efficiency, we want to limit the app so that it can only create a single instance of the class. Add a couple more instance variables:
private static ImageDataHelper dbInstance;
private Context dbContext;
Instead of using the constructor method directly, our Activities will call a factory method we define to return an instance of the class. We will use the first variable here to store the instance, so that it is only created once. The context variable will help us to avoid memory leaks, as we are going to use the application context, rather than the context for the Activity creating the database helper object.
After the constants, add a constructor method to the database helper class:
private ImageDatabase(Context context){
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
Notice that the constructor is private, so external code will not be able to call it directly. Now add a factory method so that your Activities will be able to create and access the single instance of this class:
public static ImageDataHelper getInstance(Context context) {
if (dbInstance == null)
dbInstance = new ImageDataHelper(context.getApplicationContext());
return dbInstance;
}
This is a static method, so we will be able to access it by referring to the class itself, rather than through an instance of it. We check if the database helper instance has already been created, only calling the constructor if it has not. We use the application context for efficient memory usage and return an instance of the class. You will see how we instantiate this class from Activities soon.
We will execute creation of the database table in the onCreate method, so add it:
public void onCreate(SQLiteDatabase db) {
db.execSQL(DATABASE_CREATE);
}
Here we pass the database table creation string. We also need to provide a method to execute when the database is upgraded:
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
db.execSQL("DROP TABLE IF EXISTS pics");
db.execSQL("VACUUM");
onCreate(db);
}
We destroy the existing table and create it again. If you want to change the structure of your database at any point, alter the database creation string and increment the version number - the onUpgrade method will execute.
That's our database helper class complete. Now we can create an instance of the database helper from the main Activity to use it when the app runs. In your main Activity class, add a new instance variable at the top:
1
private ImageDataHelper imgData;
Inside the onCreate method, create an instance of the new class:
1
imgData = ImageDataHelper.getInstance(this);
Notice that we use the class name and the factory method to return the instance of the database helper class, this way we know the app as a whole only has at most one instance of the class.
Step 4: Handle Clicks on the Load Button
Remember that we included a Load button for users to load in previously saved artworks. In your main Activity onCreate method, listen for clicks on the button:
Button loadBtn = (Button)findViewById(R.id.load_btn);
loadBtn.setOnClickListener(this);
Now add a new section to the conditional statement in your onClick method:
else if(v.getId()==R.id.load_btn) {
}
We will add processing to this conditional block later.
Step 5: Create a Load Class
When the user clicks the Load button, we are going to launch a pop-up style Activity which will appear on top of the main Activity. This new Activity will present the list of saved artworks in the database, allowing the user to select one to load. Create a new class in your project and name it "PicChooser" or an alternative name if you prefer. Since the content of this Activity is going to be a list of artworks, we will use a ListActivity, so extend your opening declaration line:
1
public class PicChooser extends ListActivity {
Add the following imports to the class:
import <span class="skimlinks-unlinked">android.app.ListActivity</span>;
import android.content.Intent;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import <span class="skimlinks-unlinked">android.os.Bundle</span>;
import <span class="skimlinks-unlinked">android.view.View</span>;
import android.widget.SimpleCursorAdapter;
import android.widget.TextView;
We will be using the database to list the saved pictures, so add instance variables for the database, helper and a cursor to query the data:
private ImageDataHelper picDataHelp;
private SQLiteDatabase savedPictures;
private Cursor picCursor;
Add the onCreate method:
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(<span class="skimlinks-unlinked">R.layout.load</span>);
}
Step 6: Design the Load Activity Layout
Let's add the layout we just referred to - add a new file to the "res/layout" folder and name it "load.xml" to match the code above. Add a Linear Layout to the new file:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="10dp" >
</LinearLayout>
Inside the layout, add an informative Text View and a List View to load the saved picture names into:
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/load_pics"
android:textStyle="italic" />
<ListView
android:id="@android:id/list"
android:layout_width="fill_parent"
android:layout_height="fill_parent" />
The List View ID will allow us to load data into it in Java. Add the display string indicated here to your "res/values/strings" XML file:
1
<string name="load_pics">Choose from these saved pictures:</string>
Step 7: Design the List Items
We need to define a layout for each item that will appear within the List View. Add a new layout file to your app, naming it "pic_item.xml" and including a Linear Layout:
<LinearLayout xmlns:android="http://androidninni.blogspot.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:background="#333333"
android:clickable="true"
android:onClick="picChosen"
android:orientation="horizontal"
android:padding="5dp" >
</LinearLayout>
Notice that we include an onClick attribute, specifying the name of a method we want to execute when users click the list item in question. Inside the Linear Layout, add Text Views for the ID and creation string for the picture represented:
<TextView
android:id="@+id/picID"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingRight="5dp"
android:textStyle="italic" />
<TextView
android:id="@+id/picName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textStyle="bold" />
The IDs will allow us to map data from the database to these Views.
Step 8: Query the Saved Pictures
Back in your new picture chooser Activity onCreate method, after the line in which you set the new layout, create instances of the database and helper:
picDataHelp=ImageDataHelper.getInstance(this);
savedPictures=picDataHelp.getReadableDatabase();
We use the factory method again to return the database helper instance. We will use a Cursor to traverse the records in the database table, so create it now, querying everything in the "pics" table:
1
picCursor = <span class="skimlinks-unlinked">savedPictures.query("pics</span>ll, null, null, null, null, null);
Tip: In this tutorial we are using a very simple database implementation to introduce the basics of data storage on Android. However, for more complex apps you should look into the use of Content Providers for your database operations. See this post on using Content Providers and this one on loading data with Cursor Loaders and Fragments. These will allow you to develop for efficiency by shifting your data loading operations off the app's main UI thread, but the level of complexity involved is significantly increased on the basic use we are exploring here and is therefore a little beyond the scope of this series.
For each picture in the database, we are going to list the ID and creation string. We will use a Simple Cursor Adapter to map these to the items in the List View, presenting them for users to select. We need to define the database table columns we want to display and the Views we want to map them to in the List View:
String[] columns = {ImageDataHelper.ID_COL, ImageDataHelper.CREATED_COL};
int[] views = {<span class="skimlinks-unlinked">R.id.picID</span>, R.id.picName};
We can refer to the column names using the public constants we created in the database helper class. The layout items are the two Text Views we included in the list item layout. Now we can create the Simple Cursor Adapter to map data to the visible user interface items:
SimpleCursorAdapter picAdapter = new SimpleCursorAdapter(this, R.layout.pic_item, picCursor, columns,
views, SimpleCursorAdapter.FLAG_AUTO_REQUERY);
We pass the layout we created for each list item, the cursor we created to traverse the database pictures, the columns and views we want mapped. Now we can set this as the Adapter for the List Activity:
1
setListAdapter(picAdapter);
This will cause the names and IDs of all saved pictures to be listed within the View - next we will implement selecting one from the list to load into the text-field.
Step 9: Implement Saved Picture Selection
Remember that when we created the "pic_item" layout, we specified an onClick attribute for each item in the list. When users click a list item, the specified method will execute - the method should be included in the Activity hosting the layout, and will receive the View clicked as a parameter. Add the method to your "PicChooser" Activity class:
public void picChosen(View view){
}
The View parameter is the layout for the List Item, which contains two Text Views, one for the picture ID and one for the name. We want to get the ID of the picture selected, so inside the method, get the ID Text View from the View clicked, then its text content:
TextView pickedView = (TextView)view.findViewById(<span class="skimlinks-unlinked">R.id.picID</span>);
String chosenID = (String)pickedView.getText();
The ID will allow us to retrieve the picture content from the database. Now we are going to finish the picture chooser Activity and return the chosen picture ID to the main Activity. First close the database connections:
<span class="skimlinks-unlinked">picDataHelp.close</span>();
<span class="skimlinks-unlinked">savedPictures.close</span>();
<span class="skimlinks-unlinked">picCursor.close</span>();
We are going to start this Activity running from the main Activity class, specifying that it should return a result. When this Activity ends, the onActivityResult method will therefore execute in the main class, so we can pass it the ID of the picture chosen by the user. Create an Intent and pass the data:
Intent backIntent = new Intent();
backIntent.putExtra("pickedImg", chosenID);
Set the result:
1
setResult(RESULT_OK, backIntent);
Now we can finish this Activity:
1
finish();
Before we finish with the "PicChooser" class, we need to do a bit of housekeeping. If the user selects an image from the list, we have made sure the database connections are closed before the Activity ends. However, the user may press the back button to return to the main Activity instead of choosing a picture. In that case, we can close connections in onDestroy, just add it to the class:
@Override
public void onDestroy() {
<span class="skimlinks-unlinked">picCursor.close</span>();
<span class="skimlinks-unlinked">picDataHelp.close</span>();
<span class="skimlinks-unlinked">savedPictures.close</span>();
super.onDestroy();
}
Advertisement
Step 10: Load the Chosen Picture
Now we have the facility for users to choose from the pictures stored in the database, we just need to load their chosen picture into the text-field. Back in your main Activity, add the following import statements:
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
In the else if you created for clicks on the load button of the click listener method, start the "PicChooser" Activity for a result:
Intent loadIntent = new Intent(this, <span class="skimlinks-unlinked">PicChooser.class</span>);
this.startActivityForResult(loadIntent, LOAD_REQUEST);
Note that this is similar to the code we used when launching the color configuration Activity, with a constant representing an identifier for the onActivityResult method - add the constant variable at the top of the class:
1
private final int LOAD_REQUEST=2;
Now when the user has picked a picture from the list displayed, their chosen picture ID will be returned to onActivityResult so let's work on that method. After the if statement in which you handled users returning from the color chooser Activity, add an else if with a similar outline:
else if(requestCode == LOAD_REQUEST) {
if(resultCode == RESULT_OK){
}
}
Inside this block, get the data returned from the "PicChooser" Activity:
1
String pickedID = data.getStringExtra("pickedImg");
Whenever the image displayed in the text-field is stored in the database, we will keep a record of the stored picture ID. At the top of the class, add a variable to do this:
1
private int currentPic=-1;
Initializing it to negative one will let us check whether the current image is from the database or not. Back in onActivityResult after retrieving the data from "PicChooser", update this variable:
1
currentPic=Integer.parseInt(pickedID);
We will use this when the user either deletes or edits a saved picture. Get an instance of the database from the helper:
1
SQLiteDatabase savedPicsDB = imgData.getWritableDatabase();
Query the database for the picture with the chosen ID:
Cursor chosenCursor = <span class="skimlinks-unlinked">savedPicsDB.query("pics</span>new String[]{ImageDataHelper.ASCII_COL},
ImageDataHelper.ID_COL+"=?",
new String[]{""+currentPic},
null, null, null);
Take a moment to look over this. The first parameter is the table, the second is a String array representing the columns we want, in this case just the text that makes up the ASCII picture. The third and fourth parameters are the selection, in SQL this would typically be a where query, with
the user's chosen picture ID to be matched in the ID column, so that we retrieve that particular picture.
There should only be a single record with the specified ID, so move the cursor to the first record retrieved:
1
chosenCursor.moveToFirst();
Get the text for the picture:
1
String savedChars = chosenCursor.getString(0);
Display the picture in the text-field:
1
textArea.setText(savedChars);
Close the Cursor, database and helper:
<span class="skimlinks-unlinked">chosenCursor.close</span>();
<span class="skimlinks-unlinked">savedPicsDB.close</span>();
<span class="skimlinks-unlinked">imgData.close</span>();
Conclusion
That's our database set up for storing and loading pictures. Check the source code download for anything you are unsure about. At the moment, when you run the app, you will not see any saved pictures to choose from. This is because we have not yet implemented saving pictures. We'll do that next time, in the final part of the tutorial series. We will also handle deleting pictures, creating new pictures and editing existing pictures. Then our ASCII art editor will be fully functional.
No comments:
Post a Comment