In this tutorial we are going to cover some advanced database code as well as tie in to some more advanced GUI techniques. We left off on the last tutorial showing you how to insert and select data to/from the database as well as make a table. What we need now is to be able to delete data if it is not needed and update it if we entered it incorrectly. We will tie these abilities in with some more advanced functionality utilizing a long press on the screen for delete and for updating we will just press the data we want to edit.
The project we are about to dive into is from the Android Developers website called Notepad v2 with modifications to make it geared more towards our RandomQuotes Project. We are using an already made example then modifying it because it covers more advanced ground on the GUI and database sides which is excellent for beginners and great for more advanced users to push on with. Since the items will be displayed to us in a ListView we can no longer entitle this project RandomQuote but will instead use EnhancedQuotes as our project name. Just to be clear, we will be creating a whole new project instead of copying another one over. Here is the required information below to make the project
Project Name: EnhancedQuotes
Build Target: Android 1.5
Application Name: EnhancedQuotes
Package Name: com.gregjacobs.enhancedquotes
Create Activity: QuotesMain
Min SDK Version: 3
After your project is created we can start some more advanced GUI work and integrate that with some update and delete statements. At this point, I’d like to start dividing our code into different files based on the need of the application. This is important in modern programming because it allows us to stay organized and execute functions for different screens or layouts efficiently and effectively. For this project we are going to split our code into 3 .java files and we are going to have 3 different layout files as well. We will start off with the basics by creating a new class file in our package com.gregjacobs.enhancedquotes called QuotesDBAdapter. This will contain our database code but instead of using the previous database file we created, we will start a new one. Lets look at how Google does it and see whats available other than Raw Queries from the previous tutorial.
package com.gregjacobs.enhancedquotes; import java.util.Random; import android.content.ContentValues; import android.content.Context; import android.database.Cursor; import android.database.SQLException; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.util.Log; public class QuotesDBAdapter { static Random random = new Random(); public static final String KEY_QUOTES = "quotes"; public static final String KEY_ROWID = "_id"; private static final String TAG = "QuotesDbAdapter"; private DatabaseHelper mDbHelper; private SQLiteDatabase mDb; /** * Database creation sql statement */ private static final String DATABASE_CREATE = "create table tblRandomQuotes (_id integer primary key autoincrement, " + "quotes text not null);"; private static final String DATABASE_NAME = "Random"; private static final String DATABASE_TABLE = "tblRandomQuotes"; private static final int DATABASE_VERSION = 2; private final Context mCtx; private static class DatabaseHelper extends SQLiteOpenHelper { DatabaseHelper(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(TAG, "Upgrading database from version " + oldVersion + " to " + newVersion + ", which will destroy all old data"); db.execSQL("DROP TABLE IF EXISTS tblRandomQuotes"); onCreate(db); } } /** * Constructor - takes the context to allow the database to be * opened/created * * @param ctx the Context within which to work */ public QuotesDBAdapter(Context ctx) { this.mCtx = ctx; } public QuotesDBAdapter open() throws SQLException { mDbHelper = new DatabaseHelper(mCtx); mDb = mDbHelper.getWritableDatabase(); return this; } public void close() { mDbHelper.close(); }
Looking at the code above, all of the imports should look familiar as well as everything leading up to this point. This is standard database code to implement in your Android applications. In the code below we start getting into separating our SQL statements into sections and using the functions that were stated in the previous post.
public long createQuote(String quotes) { ContentValues initialValues = new ContentValues(); initialValues.put(KEY_QUOTES, quotes); return mDb.insert(DATABASE_TABLE, null, initialValues); }
Looking at the insert statement the first variable would be the database table we are inserting into, the next variable is if we have a null set of values we would enter that here and the last is the values being inserted into the table.
public boolean deleteQuote(long rowId) { return mDb.delete(DATABASE_TABLE, KEY_ROWID + "=" + rowId, null) > 0; }
The delete statement holds three values in its method. The first variable to enter would be the database table, the second being the where statement if there was one. In this case we will need it but for some instances you may not. The last variable is the Where statement arguments but if you included them in the previous part, that will work too. It is good to note that putting “?” in your where statement and defining them in the third variable can be done as well.
public Cursor fetchAllQuotes() { return mDb.query(DATABASE_TABLE, new String[] {KEY_ROWID, KEY_QUOTES}, null, null, null, null, null); } public Cursor fetchQuote(long rowId) throws SQLException { Cursor mCursor = mDb.query(true, DATABASE_TABLE, new String[] {KEY_ROWID, KEY_QUOTES}, KEY_ROWID + "=" + rowId, null, null, null, null, null); if (mCursor != null) { mCursor.moveToFirst(); } return mCursor; }
FetchAllQuotes runs a query against the database and grabs the id and the quotes field and return all the results to a cursor. The first variable is the database table, the second is the columns the statement should return, third being the rows from the columns to return if there are any, fourth being the selection arguments, fifth is the group by SQL function, sixth is a having SQL statement, and seventh is the order by SQL function. For this we are only filling the first two and the rest can be null. The fetchQuote uses the same function but specifies what row its looking for.
public boolean updateQuote(long rowId, String title) { ContentValues args = new ContentValues(); args.put(KEY_QUOTES, title); return mDb.update(DATABASE_TABLE, args, KEY_ROWID + "=" + rowId, null) > 0; }
For the update statement we still need the database name, the new variables for any given row and finally the row number in which to update.
public int getAllEntries() { Cursor cursor = mDb.rawQuery( "SELECT COUNT(quotes) FROM tblRandomQuotes", null); if(cursor.moveToFirst()) { return cursor.getInt(0); } return cursor.getInt(0); } public String getRandomEntry() { int id = 1; id = getAllEntries(); int rand = random.nextInt(id) + 1; Cursor cursor = mDb.rawQuery( "SELECT quotes FROM tblRandomQuotes WHERE _id = " + rand, null); if(cursor.moveToFirst()) { return cursor.getString(0); } return cursor.getString(0); } }
These two functions above were mentioned last post and will be used to generate a random quote on the screen using a Toast.
Next we are going to cover all of the .xml files starting with the strings.xml file. this will contain the strings for all three of our layout XML files. The code should be pretty straight forward with already having done two or three examples. The strings.xml is as follows:
<?xml version="1.0" encoding="utf-8"?> <resources> <string name="app_name">Quotes Tracker</string> <string name="no_quotes">No Quotes Yet</string> <string name="menu_insert">Add Quote</string> <string name="menu_delete">Delete Quote</string> <string name="title">Quote:</string> <string name="confirm">Confirm</string> <string name="edit_quotes">Edit Quote</string> <string name="genRan">Generate Random Quote!</string> </resources>
After the strings.xml file we are going to move onto row.xml in the layout folder. It is not created yet so we are going to create a new XML file. We do this by right clicking on the layout folder and navigating to New and then to Other…. After this we will scroll down until we find the XML folder. Open it and double click on the file called XML. Change the name of the XML file from NewFile.xml to row.xml. The file will be created and the console may come up and present you with an error but we will fix that in a second. Now we get to the code we are going to insert into the XML file:
<?xml version="1.0" encoding="utf-8"?> <TextView android:id="@+id/text1" xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="wrap_content" android:layout_height="wrap_content"/>
The source code for this layout is a label or TextView that will insert multiple times into the main.xml for every entry we have. We will move onto the main.xml to show you how this is done.
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="wrap_content" android:layout_height="wrap_content" > <ListView android:id="@+id/android:list" android:layout_width="wrap_content" android:layout_height="wrap_content"/> <TextView android:id="@+id/android:empty" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="No Quotes!"/> <Button android:id="@+id/genRan" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="@string/genRan" /> </LinearLayout>
We are using a LinearLayout above and a ListView and a single Label that displays “No Quotes!” if the database is empty. Even though the items in the database are shown we will want to generate one randomly and that is what the button is doing at the bottom of the ListView. We can now move onto the edit.xml here which a new XML file (same spot as last time) will need to be created:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent"> <LinearLayout android:orientation="horizontal" android:layout_width="fill_parent" android:layout_height="wrap_content"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/title" /> <EditText android:id="@+id/title" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1"/> </LinearLayout> <Button android:id="@+id/confirm" android:text="@string/confirm" android:layout_width="wrap_content" android:layout_height="wrap_content" /> </LinearLayout>
Above we have one linear layout after another and that is for a very specific reason. To be able to present a neat and clean layout we must use the first linear layout to align everything vertically and fill the parent window. After that, the second linear layout will align the textbox and label horizontally. If the two linear layouts were not present the textbox would be the size of the current screen instead of the neat one line layout we have now. Other than that, the layout is pretty is basic and there should be no trouble here.
Next we are going to create a new .java file in our package com.gregjacobs.enhancedquotes called QuoteEdit and it will contain code to accept any edits we may do on our items. Below is the code and comments on the important stuff you may not know, although it should look pretty familiar because we have used almost all of these functions and methods in previous posts. Here is the code for QuoteEdit.java:
package com.gregjacobs.enhancedquotes; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.EditText; public class QuoteEdit extends Activity { private EditText mQuoteText; private Long mRowId; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.edit); mQuoteText = (EditText) findViewById(R.id.title); Button confirmButton = (Button) findViewById(R.id.confirm); mRowId = null; Bundle extras = getIntent().getExtras(); if (extras != null) { String title = extras.getString(QuotesDBAdapter.KEY_QUOTES); mRowId = extras.getLong(QuotesDBAdapter.KEY_ROWID); if (title != null) { mQuoteText.setText(title); } }
All above is pretty standard until you get to the Bundle extras = getIntent().getExtras(); part of the code. This code is pulling from the QuotesMain.java using an Intent. Now some beginners may be wondering what an Intent is. An Intent is a passive object to hold data that can pass between applications. In human-speak, its the glue that allows us to get information from the QuotesMain.java file to the QuotesEdit.java file efficiently and easily. Another new term would be a Bundle. A bundle allows use to map strings to objects such as the Intent we just talked about. So with the Bundle entitled extras, we are able to pull the data from the main .java file over to QuotesEdit.java file and vice versa.
confirmButton.setOnClickListener(new View.OnClickListener() { public void onClick(View view) { Bundle bundle = new Bundle(); bundle.putString(QuotesDBAdapter.KEY_QUOTES, mQuoteText.getText().toString()); if (mRowId != null) { bundle.putLong(QuotesDBAdapter.KEY_ROWID, mRowId); } Intent mIntent = new Intent(); mIntent.putExtras(bundle); setResult(RESULT_OK, mIntent); finish(); } }); } }
The Bundle above will package the current text in the textbox with the original ID of the object and send it back over the QuotesEdit.java to the QuotesMain.java. We are now ready to move onto QuotesMain.java where we are going to pull everything we have done so far together. This code will implement the long press on items as well as utilizing the menu button on any phone to bring up an add function. Here is the code to utilize in QuotesMain.java:
package com.gregjacobs.enhancedquotes; import android.app.ListActivity; import android.view.View.OnClickListener; import android.content.Context; import android.content.Intent; import android.database.Cursor; import android.os.Bundle; import android.view.ContextMenu; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.ContextMenu.ContextMenuInfo; import android.widget.ListView; import android.widget.Button; import android.widget.SimpleCursorAdapter; import android.widget.Toast; import android.widget.AdapterView.AdapterContextMenuInfo;
Above we have a few new imports to be able to use the more advanced items in this project such as intents, menu’s and menuitem, listview and simplecursoradapters. These will all be explained as they come up.
public class QuotesMain extends ListActivity { private static final int ACTIVITY_CREATE=0; private static final int ACTIVITY_EDIT=1; private static final int INSERT_ID = Menu.FIRST; private static final int DELETE_ID = Menu.FIRST + 1; private QuotesDBAdapter mDbHelper; private Cursor mNotesCursor; public Button button; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); mDbHelper = new QuotesDBAdapter(this); mDbHelper.open(); fillData(); registerForContextMenu(getListView()); button = (Button)findViewById(R.id.genRan); button.setOnClickListener(mAddListener); }
We are making variables for creating, editing, inserting and deleting and making them static because they are not going to change. In the onCreate function we utilize fillData() which will be defined below. As well you will notice that we register the listview items in the context menu and set a listener for the button. A context menu is best described as kind of a pop-up menu and this will be utilized when we want to delete a item within the listview.
private OnClickListener mAddListener = new OnClickListener() { public void onClick(View v) { //long id1 = 0; // do something when the button is clicked try { String quote = ""; quote = mDbHelper.getRandomEntry(); Context context = getApplicationContext(); CharSequence text = quote; int duration = Toast.LENGTH_LONG; Toast toast = Toast.makeText(context, text, duration); toast.show(); } catch (Exception ex) { Context context = getApplicationContext(); CharSequence text = ex.toString(); int duration = Toast.LENGTH_LONG; Toast toast = Toast.makeText(context, text, duration); toast.show(); } } }; private void fillData() { // Get all of the rows from the database and create the item list mNotesCursor = mDbHelper.fetchAllQuotes(); startManagingCursor(mNotesCursor); // Create an array to specify the fields we want to display in the list (only TITLE) String[] from = new String[]{QuotesDBAdapter.KEY_QUOTES}; // and an array of the fields we want to bind those fields to (in this case just text1) int[] to = new int[]{R.id.text1}; // Now create a simple cursor adapter and set it to display SimpleCursorAdapter notes = new SimpleCursorAdapter(this, R.layout.row, mNotesCursor, from, to); setListAdapter(notes); }
The button function above is exactly like the previous one that is used to generate a random quote from our list. The new method fillData() as mentioned above is going to be used to get all of the quotes and bind the ID and the actual quote together and add them to the listview using the SimpleCursorAdapter. The SimpleCursorAdapter is used to bind bind columns in a returned cursor to any text we place on the screen.
@Override public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); menu.add(0, INSERT_ID,0, R.string.menu_insert); return true; } @Override public boolean onMenuItemSelected(int featureId, MenuItem item) { switch(item.getItemId()) { case INSERT_ID: createNote(); return true; } return super.onMenuItemSelected(featureId, item); }
In the first function above called onCreateOptionsMenu() we are adding the ability to add an item to the database using the menu press option that will bring up dialog asking if we would like to do this. If this completes successfully then the statement will return true. The one below it checks to see if an item has been pressed in the menu. If it has it uses a switch statement to check the value that we defined above. If it matches then we create a note which is defined below.
@Override public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { super.onCreateContextMenu(menu, v, menuInfo); menu.add(0, DELETE_ID, 0, R.string.menu_delete); } @Override public boolean onContextItemSelected(MenuItem item) { switch(item.getItemId()) { case DELETE_ID: AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo(); mDbHelper.deleteQuote(info.id); fillData(); return true; } return super.onContextItemSelected(item); } private void createNote() { Intent i = new Intent(this, QuoteEdit.class); startActivityForResult(i, ACTIVITY_CREATE); }
The function above is used to register the context menu and give the option to delete items using the menu.add function as seen above as well as here. If the context menu item Delete is pressed then the database helper will delete the quote based on the ID. The createNote() function uses an intent to pass the application over to the QuoteEdit file and load a new screen and when done a new intent will send the completed data back over here so we can add it to the listview.
@Override protected void onListItemClick(ListView l, View v, int position, long id) { super.onListItemClick(l, v, position, id); Cursor c = mNotesCursor; c.moveToPosition(position); Intent i = new Intent(this, QuoteEdit.class); i.putExtra(QuotesDBAdapter.KEY_ROWID, id); i.putExtra(QuotesDBAdapter.KEY_QUOTES, c.getString( c.getColumnIndexOrThrow(QuotesDBAdapter.KEY_QUOTES))); startActivityForResult(i, ACTIVITY_EDIT); }
If an item from the listview is pressed the function above is loaded to initialize an intent and put the information into the intent and pull it over to the QuoteEdit class to be edited. When completed the QuoteEdit class will send the completed data back over and we can continue to add, edit or delete more items.
@Override protected void onActivityResult(int requestCode, int resultCode, Intent intent) { super.onActivityResult(requestCode, resultCode, intent); Bundle extras = intent.getExtras(); switch(requestCode) { case ACTIVITY_CREATE: String title = extras.getString(QuotesDBAdapter.KEY_QUOTES); mDbHelper.createQuote(title); fillData(); break; case ACTIVITY_EDIT: Long rowId = extras.getLong(QuotesDBAdapter.KEY_ROWID); if (rowId != null) { String editTitle = extras.getString(QuotesDBAdapter.KEY_QUOTES); mDbHelper.updateQuote(rowId, editTitle); } fillData(); break; } } }
The method above takes the result of an activity and uses the result to utilize a specific method. The result in this case would either be creating a new quote or editing an existing one. The basis of this switch statement is to utilize the database helper and either insert data or update data within the database.
We now have one more file to go over before we could run our application on the emulator. This would be the AndroidManifest.XML file and that will control what is registered and what runs, it is essentially the heart of the program and we need it to recognize that we have 2 parts to our application. Here is the code for the AndroidManifest:
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.gregjacobs.enhancedquotes"> <application android:icon="@drawable/icon"> <activity android:name=".QuotesMain" android:label="@string/app_name"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <activity android:name=".QuoteEdit" android:label="@string/app_name"></activity> </application> </manifest>
If your code doesnt look like this then you will have to do some modifications to your code to talor it to this. You will notice that we have added another Activity to the manifest file and are giving it the same name as the one above, app_name. This will denote that we have another activity that we would like to use and we register it here. Also, you will notice that the uses-sdk android:minSdkVersion=”3″ has been taken out. This is because an application does not really need it but it is always good to have. This just uses it as a reference and will use the specified build target when building your application.
The application should build and you will be able to try out the more advanced features of Android programming. The possibilities are endless with the knowledge you learn but what if your database/database code is not working?
Thats what the Dalvick Debug Monitor Server (DDMS) is for. When the emulator is running we are able to switch over to the DDMS by going to the top right of your screen and pressing the >> and then clicking on DDMS. If you are new to Android Development this new screen will be very confusing for you. What we are going to take out of going to the DDMS for right now is the ability to add and take from our emulator items which may be of interest. For this particular tutorial we are going to grab a database from the running emulator.
Before we get started we will need to download some software I find very useful for SQLite developers. This being SQLite Database Browser (SDB). This software will allow you to open SQLite databases and explore the contents, even modifying them through SQL statements. Once the file is downloaded find the folder and click on the .exe to start it up. Leave this program up and we will get back to it later.
To be able to put them into the SDB we need to pull them off the emulator. To do this we have to delve into the running emulator and find the database we want. It is key to remember that databases are application specific so we will need to find the package name and the database will be under a Database Folder. When in DDMS goto the devices tab and click on our emulator. then in the middle of the program should be a tab called File Explorer. Once File Explorer has been clicked we will now see three folders (maybe more depending on what you do with the device) called data,sdcard and system. We will leave system and sdcard alone for right now as we are going to use the data folder so open it. Once open, navigate to another folder called data and open it too. We are now presented with the package names with everything installed on our emulator. Navigate to com.gregjacobs.enhancedquotes and open it. Once open the two folders that appear should be databases and lib. Open databases folder and take the file called Random. Now to be able to take this file we are going to click on it once then navigate to the top of the tab and press the button that looks like a floppy disc with an arrow pointing to the left. Once this icon is clicked a dialog box will appear asking where you want to save the selected file. Choose an easy to locate place and click save.
One the file has been taken from the emulator we are going to go back to SDB and click the big open button, find our file we saved and click open. Once the file is open we are able to see the structure of the database and navigate to browse the data. to do this we are going to click on the tab called Browse Data and in the dropdown that says table beside it, we are going to choose tblRandomQuotes. The data in the table will now appear and now you know where to find your data if you ever need to modify something an put it back onto the emulator. The SDB is also good for testing out SQL queries if you are unsure of what the data returned would be. This will be an invaluable tool if you do database applications in Android.
Here are the files from my project for comparison:
AndroidManifest.xml | edit.xml | main.xml | QuoteEdit.java | QuoteMain.java | QuotesDBAdapter.java | row.xml | strings.xml
Now that you have an advanced understanding of some of the GUI options available to you and Database code covered in more detail in this tutorial, you are ready to start making some applications that have a little more depth than just button clicks. With the understanding you have of Intents and Bundles, you can make your programs well rounded and divide your code and layouts to match what your looking to make. If anyone has an idea that they have implemented since following this tutorial feel free to send them to me us so we can check out what you have learned. The next tutorial will cover designing the statistics tracker and using DroidDraw to develop some of the UI. Until the next tutorial, Happy Hacking!
Articles Used For Reference:
Google Notepad Tutorial – NotepadV2
Continue on to Part 5: DroidDraw & Information Tracker Complete
“If your code doesnt look like this then you will have to do some modifications to your to talor it to this. You will notice that we have added another to the manifest an are giving it the same name as the one above, app_name. This will denote that we have another activity that we would like to use and we register it here. Also, you will notice that the has been taken out. this is because an application does not really need it but it is always good to have.”
this section just caught my eye – there are at least the sentences that don’t make any sense…
It might be time to consider multiple page articles. ;)
It might be time to avoid software howtos. ;)
ohh can an upcoming part include barcodes and qr code ? would be a nice add on to this
Please can you make the RSS feed only show a synopsis of articles. Many other sites do it and although most articles are worth reading all the way through, I visit the site to read them, I don’t want the whole thing appearing in my RSS feed.
Mowcius
Just came through to say the same as mowcius – it makes scrolling through an RSS feed fairly arduous when an article this long is shown in its entirety.
Sorry guys. I just changed the wordpress settings to show only a summary in the feeds. I see no difference though. We’ll figure it out.
@ AS & Mowcius: It’s called shortcuts. I’m sure there is a “next article” key for your reader. Google Reader: J to go to next article. K to go back. Simple.
Screw this. I’m waiting for my AppInventor invite to arrive.
How about making two feeds, one with the short version of each article and one with the complete article as before? I enjoyed being able to read articles with google reader instead of having to click on each article. I even read articles I wouldn’t have read otherwise.
I have an android phone and I love it (posting from it now), and I enjoy writing apps for the platform. However, this Android SDK tutorial series (and software tutorials in general) have no place on HaD. There is no shortage of information on Androis app development. This isn’t challenging, impressive, or even remotely interesting. Put simply, it lacks hack value.
I sincerely hope that HaD does not continue in this direction. This content is far more appropriate for a site like Lifehacker.
“Sorry guys. I just changed the wordpress settings to show only a summary in the feeds. I see no difference though. We’ll figure it out.”
Nooo? WTF? Because two guys whined? What about the rest of us, who loved the full articles?
You don’t have to use a feature just because it’s there :)
@Odd Rune,
It was a test run, the feed should be back to normal. Not sure how to make 2 feeds, so it will stay this way for a while.
Well, I like it.. Nice work. I’m a C# .net dev and this is helpful for explaining the diffs in the IDE as well as how android thinks.
@r_d, qwerty and everyone else that wants to bitch about software how-to’s/ I personally enjoy the content. It’s nice to have something original here instead of just a rehash of makezine.
That said, you can’t do software hacking without the tools to do so any more than you can solder with the tools and education to do that. I think a how-to solder article would be just as appropriate.
Just because it teaches a generalized base skill instead of a specific hack doesn’t make it any less valid for the site.
@Jonas – Sorry I didn’t notice it before but I had enclosing what I wrote and WordPress omitted them thinking they were functions. Here is the before and after shot of revisions:
Before:
If your code doesnt look like this then you will have to do some modifications to your to talor it to this. You will notice that we have added another to the manifest an are giving it the same name as the one above, app_name. This will denote that we have another activity that we would like to use and we register it here. Also, you will notice that the has been taken out. this is because an application does not really need it but it is always good to have.
After:
If your code doesnt look like this then you will have to do some modifications to your code to talor it to this. You will notice that we have added another Activity to the manifest file and are giving it the same name as the one above, app_name. This will denote that we have another activity that we would like to use and we register it here. Also, you will notice that the uses-sdk android:minSdkVersion=”3″ has been taken out. This is because an application does not really need it but it is always good to have.
i tried executing the above code,i am getting java.Lang.IllegalArgumentException
What’s happening with the following code? It looks like you’re returning cursor.getInt(0) whether the statement is true or false. Why have the if statement at all then?
if(cursor.moveToFirst()) {
return cursor.getInt(0);
}
return cursor.getInt(0);
@Michael – We use the moveToFirst() method as a precaution to move the cursor to the first row. If the cursor is already there then we wouldn’t go through the if statement. Try taking it out and see if it works and let me know :) I am always overcautious when writing code (saves me from going back later and rewriting code) but if it works without then I would see no problem doing it without the if statement :)
I was getting errors if I deleted a quote and then clicked the “Generate Random Quote” button. The query was trying to select records where the _id no longer existed. I added a new method called getIds and changed the getRandomEntry method so that it makes a random selection from the available _id records.
I’m still not sure about the if statement. This example works fine if you do this:
cursor.moveToFirst();
return cursor;
I do think it’s a good idea to check if cursor.moveToFirst() succeeds, but I don’t know what conditions would cause it to fail or how to handle it gracefully if it does. Anyway, here are my changes:
public Cursor getIds() {
Cursor cursor = mDb.query(true, DATABASE_TABLE, new String[] {KEY_ROWID}, null,null,null,null,null,null);
cursor.moveToFirst();
return cursor;
}
public String getRandomEntry()
{
Cursor aCursor = getIds();
int max = aCursor.getCount();
int rand = random.nextInt(max);
aCursor.moveToPosition(rand);
int rId = aCursor.getInt(0);
Cursor cursor = mDb.rawQuery(“SELECT quotes FROM tblRandomQuotes WHERE _id = ” + rId, null);
if (cursor.moveToFirst()) {
return cursor.getString(0);
} else {
return “MoveToFirst Failed! Handle this somehow!”;
}
}
I’m working on an application in which I have about 110mb of text data I want to be able to provide to users of the application. I tried putting it in a json file but found that there is an ~16mb limit for my application (at least that is where the emulator barfed). Any ideas or suggestions on how to be able to display the data without running out of memory? I was partially thinking of using multiple json files and deleting old data when I need to bring in new data, but still seems like it’ll be a PITA; especially since I’ll want to do searches….
Check out videos at http://www.youtube.com/user/Jaynonymous1 if you want to see more of what you can do with SQLite.
There is a recurring bug if you delete any records, there is a chance of getting a CursorIndexOutOfBoundsException.
The problem is that you use a Random number generated outside the database table to determine what record _id to select, but the autoincrement key doesn’t re-use numbers once they’ve been deleted.
Therefore the higher percentage of deletes you have versus the number of records in your table, the higher chance you have of getting the error.
To reproduce:
Add a new Quote(first record in db _id=1)
Delete that quote
Add another new quote(only record in db _id=2)
click Generate and you have a 50% chance of getting the error.
Anyway, the easy fix is to replace your getRandomEntry() method with the following:
Hope this helps.
-Brett
doh.. 1 bug.
change that to :
During OnCreate the database gets created and opened but fillData calls fetchAllQuotes which causes an SQL exception
“no such column: quotes: , while compiling: SELECT _id, quotes FROM tblRandomQuotes”
Any help appreciated, I’ve been trying for two days to resolve this.
Got it, the first time I ran the code in the emulator I must have got far enough to create the table “quotestext” as a typo but despite correcting the typo because the table had already been created in the database it persisted and did not get over-written or replace it with the correct table “quotes” if type text.
Should have dived into the SQL browser section quicker.
Thanks again for the examples.
2 questions:
1) How do I get the delete function to work? I press menu I get add quote but how do I call the delete quote?
2) If I am inside add quote and then press back (the curve arrow icon) I get an error.
Am I missing something?
Hello, I love your tutorials thanks for doing them they are very helpful… I have completed the database tutorial, and the application works, but when I try to use DDMS with the emulator running, nothing shows up under the devices tab in DDMS, and also there is nothing in the File Explorer.
There was when I first tried it but the app crashed due to a type. I was able to locate the proble in the LogCat but the device and files have disappeared, even if I restart Eclipse, the emulator, and DDMS… any suggestions? I would really like to be able to see the database structure in the tool you provided.
Thanks again, any help for me is greatly appreciated!
Ok tried reseting the ADB and that got the emulator to show under the devices tab, but when I click on the ‘data’ folder under file explorer tab, all items vanish – seems to be buggy for some reason, any clue what to do?
So I keep having the same problem after resetting the adb, I can open some of the folders, but everything disappears very quicky before I can grab the database file… this message is display in the console every time:
“[2010-10-09 20:58:14 – DeviceMonitor]Sending jdwp tracking request failed!
[2010-10-09 20:58:58 – DeviceMonitor]Adb connection Error:An existing connection was forcibly closed by the remote host
[2010-10-09 20:58:59 – DeviceMonitor]Connection attempts: 1
[2010-10-09 20:59:11 – DeviceMonitor]Sending jdwp tracking request failed!”
okay… sorry to be cluttering up your comment section – I finally grabbed it, I had to add some info to the manifest file like these:
and these attributes into the manifest element:
android:versionCode=”1″
android:versionName=”1.0″>
and that seemed to buy me enough time to quicky race to the db file and pull it, but the adb still disconnected and files disappeared shortly afterwards… from reading forums I am guessing it is a problem with the manifest declarations… Not sure if you want to publish all this stuff but I just thought I’d share, so yeah, FYI…
Tks for post!
Found and fixed 2 bugs that were causing crash:
1) Cancelling (BACK button) the add quote activity
protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
super.onActivityResult(requestCode, resultCode, intent);
if(resultCode == RESULT_CANCELED)
{ Log.d(“TAG”, ” Activity “+intent+ ” CANCELED!”); // quick hack
return;}
2) Delete a Quote had a problem .. db not open!
public boolean onContextItemSelected(MenuItem item) {
switch(item.getItemId()) {
case DELETE_ID:
mDbHelper.open();
AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo();
mDbHelper.deleteQuote(info.id);
fillData();
return true;
So i tried out this code, and it seems theres a bug with editing a quote if it has been added since one has been deleted. It looks like the locations are not lining up. I added a quote, deleted it and then added a new one. When I go to edit the new quote, the app crashes, is this because the position says it its the first but the _id is really 2?
Thanks for the help
Nevermind, found out what I was doing wrong, I moved the edit option into the context menu and wasn’t reading from the database when I wanted to edit, I was trying to use the mNotesCursor, and thats why things weren’t lining up
hello,
I tried this code but have some small error for below code mDbHelper.createQuote(title); this line createQuote error found so please tell mi what i am do
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
super.onActivityResult(requestCode, resultCode, intent);
Bundle extras = intent.getExtras();
mDbHelper.open();
switch(requestCode) {
case ACTIVITY_CREATE:
String title = extras.getString(QuotesDBAdapter.KEY_QUOTES);
mDbHelper.createQuote(title);
fillData();
break;
case ACTIVITY_EDIT:
Long rowId = extras.getLong(QuotesDBAdapter.KEY_ROWID);
if (rowId != null) {
String editTitle = extras.getString(QuotesDBAdapter.KEY_QUOTES);
mDbHelper.updateQuote(rowId, editTitle);
}
fillData();
break;
}
mDbHelper.close();
}
}
Excellent work! Thanks heaps for putting this up.
The code works fine for me except for one little detail. The default text ‘No Quotes!’ that is supposed to be shown when there are no quotes in the database is always showing underneath the quotes on my system.
Any idea where this might have come from?
I have followed all the tutorial, but when i execute on the emulator the application stop unexpectedly and i need to force close it.
I did an execution with the debugger to find at wich line it’s happening.
It’s happening in the fillData() function at the line:
SimpleCursorAdapter notes = new SimpleCursorAdapter(this, R.layout.row, mNotesCursor, from, to);
If someone have a clue for me? I really don’t understand what I did wrong.
Hi,
Do you have any tutorial on Google maps and gps?
Thanks for the helpful tutorial.
Hi,
Manny thnx for the tutors so far. I cant wait to continue tomorrow ^_^
nice tutorial. but i was getting a force close for the context menu(on long press). solved it by putting mDbHelper.open(); in QuotesMain.java file in OnContextItemSelected method (before mdbhelper.deletequote).