mgmblog.com

February 6, 2009

Export an Android SQLite db to an XML file on the SD Card

Filed under: Android, Toolbox — Tags: , , — michael @ 12:50 am

I’m pretty syched about this code.  It took some figuring out and some hunting and asking questions on Google’s Android forum.  But thanks to the following posts

 File permissions for world read/write

SQLite3 sql command to show tables in database

I pieced together the beginnings of some code that will take an Android SQLite db and export it to XML data within a file on the SD Card.  This has a couple potential uses,

  • Backing up program data for importing later
  • Archiving some of the data for an application for possible reprocessing later
  • Archive some data to shrink the memory footprint of the application
  • Export data to a web server
    • Data could be further broken down so only updated rows since last upload would be sent to server
  • (fill in your idea here)

The code is still a little raw, not much error checking or exception handling, but I’m sure you can improve on it in those areas.  I just wanted to get it out so I didn’t have any more excuses.  The DatabaseAssistant class is called from your application and given the database to download.

Can anybody point out any obvious issues here (again, other then error/exception handling).  Hope you can make some use of it, sorry about the funky formatting, someday I’ll get around to a better way of adding code.

public class DatabaseAssistant
{
	private static final String EXPORT_FILE_NAME = "/sdcard/export.xml";

	private Context _ctx;
	private SQLiteDatabase _db;
	private Exporter _exporter;

	public DatabaseAssistant( Context ctx, SQLiteDatabase db )
	{
		_ctx = ctx;
		_db = db;

		try
		{
			// create a file on the sdcard to export the
			// database contents to
			File myFile = new File( EXPORT_FILE_NAME );
                        myFile.createNewFile();

                        FileOutputStream fOut =  new FileOutputStream(myFile);
                        BufferedOutputStream bos = new BufferedOutputStream( fOut );

			_exporter = new Exporter( bos );
		}
		catch (FileNotFoundException e)
		{
			e.printStackTrace();
		}
		catch (IOException e)
		{
			e.printStackTrace();
		}
	}

	public void exportData( )
	{
		log( "Exporting Data" );

		try
		{
			_exporter.startDbExport( _db.getPath() );

			// get the tables out of the given sqlite database
	    	        String sql = "SELECT * FROM sqlite_master";

	    	        Cursor cur = _db.rawQuery( sql, new String[0] );
	    	        Log.d("db", "show tables, cur size " + cur.getCount() );
	    	        cur.moveToFirst();

	    	        String tableName;
	    	        while ( cur.getPosition() < cur.getCount() )
	    	        {
	    		        tableName = cur.getString( cur.getColumnIndex( "name" ) );
	    		        log( "table name " + tableName );

	    		        // don't process these two tables since they are used
	    		        // for metadata
	    		        if ( ! tableName.equals( "android_metadata" ) &&
	    				! tableName.equals( "sqlite_sequence" ) )
	    		        {
	    			        exportTable( tableName );
	    		        }

	    		        cur.moveToNext();
	    	        }
		        _exporter.endDbExport();
			_exporter.close();
		}
		catch (IOException e)
		{
			e.printStackTrace();
		}
	}

	private void exportTable( String tableName ) throws IOException
	{
		_exporter.startTable(tableName);

		// get everything from the table
		String sql = "select * from " + tableName;
		Cursor cur = _db.rawQuery( sql, new String[0] );
		int numcols = cur.getColumnCount();

		log( "Start exporting table " + tableName );

//		// logging
//		for( int idx = 0; idx < numcols; idx++ )
//		{
//			log( "column " + cur.getColumnName(idx) );
//		}

		cur.moveToFirst();

		// move through the table, creating rows
		// and adding each column with name and value
		// to the row
		while( cur.getPosition() < cur.getCount() )
		{
			_exporter.startRow();
			String name;
			String val;
			for( int idx = 0; idx < numcols; idx++ )
			{
				name = cur.getColumnName(idx);
				val = cur.getString( idx );
				log( "col '" + name + "' -- val '" + val + "'" );

				_exporter.addColumn( name, val );
			}

			_exporter.endRow();
			cur.moveToNext();
		}

		cur.close();

		_exporter.endTable();
	}

	private void log( String msg )
	{
		Log.d( "DatabaseAssistant", msg );
	}

	class Exporter
	{
		private static final String CLOSING_WITH_TICK = "'>";
		private static final String START_DB = "<export-database name='";
		private static final String END_DB = "</export-database>";
		private static final String START_TABLE = "<table name='";
		private static final String END_TABLE = "</table>";
		private static final String START_ROW = "<row>";
		private static final String END_ROW = "</row>";
		private static final String START_COL = "<col name='";
		private static final String END_COL = "</col>";

		private BufferedOutputStream _bos;

		public Exporter() throws FileNotFoundException
		{
			this( new BufferedOutputStream(
					_ctx.openFileOutput( EXPORT_FILE_NAME,
					Context.MODE_WORLD_READABLE ) ) );
		}

		public Exporter( BufferedOutputStream bos )
		{
			_bos = bos;
		}

		public void close() throws IOException
		{
			if ( _bos != null )
			{
				_bos.close();
			}
		}

		public void startDbExport( String dbName ) throws IOException
		{
			String stg = START_DB + dbName + CLOSING_WITH_TICK;
			_bos.write( stg.getBytes() );
		}

		public void endDbExport() throws IOException
		{
			_bos.write( END_DB.getBytes() );
		}

		public void startTable( String tableName ) throws IOException
		{
			String stg = START_TABLE + tableName + CLOSING_WITH_TICK;
			_bos.write( stg.getBytes() );
		}

		public void endTable() throws IOException
		{
			_bos.write( END_TABLE.getBytes() );
		}

		public void startRow() throws IOException
		{
			_bos.write( START_ROW.getBytes() );
		}

		public void endRow() throws IOException
		{
			_bos.write( END_ROW.getBytes() );
		}

		public void addColumn( String name, String val ) throws IOException
		{
			String stg = START_COL + name + CLOSING_WITH_TICK + val + END_COL;
			_bos.write( stg.getBytes() );
		}
	}

	class Importer
	{

	}

}

18 Comments »

  1. Michael,

    It’s great. Thanks. No complains… :-) I mean, no suggestions. Have you heard about a similar feature but to import from an XML into SQLite?

    Stan Berka

    Comment by Stan Berka — February 13, 2009 @ 1:22 pm

  2. Hey Stan,

    Thanks for the comment! Yea, the next step is certainly importing. I’d also like to put in mechanism to only export changed data. That way, this can be used to upload data to a server where it can be stored, aggregated, and synchronized with other users and/or devices.

    - michael

    Comment by michael — February 13, 2009 @ 1:32 pm

  3. How/why did you come up with “new String[0]” in rawQuery() ? Did “null” not work? (As an aside, have you ever seen replacement params in rawQuery work at all?)

    Comment by Ward — February 19, 2009 @ 10:23 am

  4. Hey Ward,

    I believe null would work as well, I’ll have to do some more research on that exact method to make a 100% statement one way or the other.

    - michael

    Comment by michael — February 19, 2009 @ 11:29 am

  5. Hi am new to android and just looking at your code. How do i call the DatabaseAssistant class?

    Comment by sam — March 6, 2009 @ 11:33 am

  6. Excellent stuff… Ran a few tests and noticed that you have to be careful when re-constituting as the columns that were exported may have unfriendly to XML chars (so it will not be just read file, create doc if so.) Good stuff!

    Comment by Mike — March 13, 2009 @ 10:18 pm

  7. That’s great!!!!!! Haven’t tested yet but I’m sure I can hack my way through any problems I encounter. Thanks a lot!!!!!

    Comment by will — April 5, 2009 @ 12:49 pm

  8. Lovely! Works great. Thanks for posting!

    Comment by Anna — April 16, 2009 @ 4:13 pm

  9. hi, its cool, exactly what i was looking for.

    please can you also publish the READ Class… i am quit sure you also have one :)
    than it makes sense.

    greets
    chris

    Comment by chris — April 29, 2009 @ 12:07 pm

  10. hi,, great work, but please can you also provide a READ source
    chris

    Comment by chris — May 2, 2009 @ 6:33 am

  11. How can i do if i have a large db .
    can i store directly into the SD CARD directly instead of internal memory .
    how can i do it …
    sqliteio exception i am getting after some time

    Comment by vinod — September 1, 2009 @ 1:42 am

  12. Be sure to check for illegal characters before you write it to XML, something like this:

    // Check for any illegal characters
    public String checkForIllegalChars(String val) {
    String newStr = val;

    if (newStr.contains(”&”)) { //make sure we do this check first
    newStr = newStr.replaceAll(”&”, “&”);
    }

    if (newStr.contains(”<”)) {
    newStr = newStr.replaceAll(”")) {
    newStr = newStr.replaceAll(”>”, “>”);
    }

    if (newStr.contains(”\”")) {
    newStr = newStr.replaceAll(”\”", “"”);
    }

    if (newStr.contains(”‘”)) {
    newStr = newStr.replaceAll(”‘”, “'”);
    }

    return newStr;
    }

    Comment by Jamie — October 14, 2009 @ 2:04 pm

  13. This is great…
    Thank you very much!

    Comment by 0x1b — November 17, 2009 @ 6:40 pm

  14. Thanks! this has help me alot. You’re the man!!! By the way, did you get the importing the database back from the xml done as well?

    Comment by Trebak — February 10, 2010 @ 3:05 pm

  15. Excellent. By any chance did you do a tutorial on importing the data from xml?

    Comment by Trebak — February 10, 2010 @ 5:49 pm

  16. One again, your idea is very

    good.thank you!very much.

    Comment by nike dunk shoes — April 22, 2010 @ 10:25 pm

  17. Hey, this is an awesome way, but i everytime stops to work. how do i have to call the class properly?

    Comment by ckaatz — May 20, 2010 @ 3:03 am

  18. what do i pass as context? beginner obviously

    Comment by Usmaan — December 30, 2010 @ 6:38 am

RSS feed for comments on this post. TrackBack URL

Leave a comment

Powered by WordPress