Titanium Mobileで作る! iPhone/Androidアプリ

第10回データベースの使用

データベースを使ってみる

前回の記事ではTitanium.App.Propertiesやファイル入出力を利用したデータの永続化について解説しましたが、今回はRDBMSを利用したデータの扱い方について解説します。具体的には、自分のホーム画面のタイムラインを取得保存し、前回起動時に取得した分のタイムラインも閲覧できたり、タイムラインの内容を検索できるようにしてみます。

タイムラインの取得

この実装のために今までのサンプルコードを一部改変します。最初の画面で自分の発言だけでなく、フォローしている人も含めたタイムラインが閲覧できるようにtable_view.jsのタイムライン取得部分に次のような変更を加えています。

Ti.include("lib/twitter_api.js");
Ti.include("twitter_settings.js");
Ti.include("tweet_db.js");
//initialization
Ti.App.twitterApi = new TwitterApi({
    consumerKey: TwitterSettings.consumerKey,
    consumerSecret: TwitterSettings.consumerSecret
});
var twitterApi = Ti.App.twitterApi;
twitterApi.init(); 

twitterApi.statuses_home_timeline(
    {
        onSuccess: function(response){
            updateTimeline(db.savedTweets());
        },
        onError: function(error){
            Ti.API.error(error);
        }
    }
);

今まではmessage_window.jsのほうにあったTwitterApiオブジェクトの初期化をtable_view.jsに移動しています。また、タイムラインの取得をTwitterApiオブジェクトを通して実行するようにしています。

自分のタイムラインが表示されるようになりました
自分のタイムラインが表示されるようになりました

Titanium.Database

さて、TitaniumにはTitanium.Databaseというオブジェクトを通してRDBMSにアクセスする機能が実装されており、iPhoneにもAndroidにもライブラリが搭載されているSQLiteを利用することができます。SQLiteはパブリックドメインのRDBMSで、軽量かつ高速に動作することから様々な分野でよく利用されているRDBMSです。MySQLなどほかのRDBMSに慣れている方であればそれほど苦なく扱うことができると思います。

では今回は、データベースに接続するためのオブジェクトをまず先に作ってしまいましょう。全く新しいファイルtweet_db.jsをResourcesディレクトリに作成し、次のコードを実装します。このオブジェクトを利用するときは関数TweetDBをnewして利用します。

var TweetDB = function() {
    this.dbName = 'tweetdb'; //(1)
    
    this.open = function () { //(2)
        this.db = Titanium.Database.open(this.dbName);
    };

    this.close = function () { //(3)
        this.db.close();
    };

    this.addTweets = function (tweets) { //(4)
        this.open();
        for (var i=0;i<tweets.length;i++) {
            var tweet = tweets[i];
            var rows = this.db.execute( //(5)
                'SELECT * FROM tweets WHERE status_id = ?',
                tweet.id_str
            );
            Ti.API.debug('Found: ' + rows.getRowCount() );
            if ( rows.getRowCount() > 0 ) continue;

            var res = this.db.execute(
                'INSERT INTO tweets (screen_name, profile_image_url, tweet_text, status_id, created_at) VALUES(?,?,?,?,?)',
                tweet.user.screen_name,
                tweet.user.profile_image_url,
                tweet.text,
                tweet.id_str,
                tweet.created_at
            );
            Ti.API.debug('Add to DB');
        }
        this.close();
        return true;
    };


    this.open();
    this.db.execute('CREATE TABLE IF NOT EXISTS tweets (screen_name TEXT, profile_image_url TEXT, tweet_text TEXT, status_id TEXT, created_at TEXT)');
    this.close();
};

では、先頭部分から解説していきましょう。最初に(1)の部分で接続するデータベースの名前をオブジェクのプロパティにセットします。

さらに次の部分(2), (3)でopenとcloseメソッドを定義しています。このメソッドはデータベースの処理を始めるときのTitanium.Database.openの処理と、終了するときのcloseの処理を簡単に呼び出せるようにしているだけです。openメソッドの中で使われている、Titanium.Database.openメソッドがデータベース接続を始めるメソッドで、このメソッドが返すTitatnium.Databaseオブジェクトに対して、各データベース処理を行っていくことになります。

この次に(4)で定義されているaddTweetメソッドは、タイムラインの情報をもった配列tweetsを受け取ることで、その中から保存が必要なものだけをデータベースに保存するメソッドです。まずthis.open()でデータベースに接続します。その後タイムライン中の各ツイートに対して、既に保存されていないか確認し(A)、保存されていなかった場合はデータベースに保存する(B)、という処理をしています。(A)の部分を行っているのが、(5)の

var rows = this.db.execute(
  'SELECT * FROM tweets WHERE status_id = ?',
   tweet.id_str
);
Ti.API.debug('Found: ' + rows.getRowCount() );
if ( rows.getRowCount() > 0 ) continue;

の部分で、this.dbに保存されているTi.DataBaseオブジェクトに対してSQLのSELECT文を発行しています。第一引数のSQL文に含まれる '?' は、第二引数以降に変換される、いわゆるプレースホルダです。

SQLを発行した結果なにかデータが返ってきている場合は、executeメソッドの返り値オブジェクトTitanium.Database.ResultSetのgetRowCount()メソッドで確認できます。この例ではデータが返っていた場合、つまりgetRowCount()が0より大きかった場合はすでにそのツイートが保存されている状態なので、何もせず次のツイートを確かめるように実装しています。

さらに次に続く部分が、(B)のデータベースにデータを保存している部分で、INSERT文を発行することでデータベースの各カラムにツイートの情報を保存しています。

タイムライン中の全てのツイートに対してこの処理を行った後、this.close()を呼び出して、データベースへの接続を終了しています。

もう一つ重要なのは、saveTweetメソッドの定義のあとで、

    this.db.execute('CREATE TABLE IF NOT EXISTS tweets (screen_name TEXT, profile_image_url TEXT, tweet_text TEXT, status_id TEXT, created_at TEXT)');

という文がある点です。この文は、このTweetDBオブジェクがnewされた時に一度だけ実行されるもので、tweetsというテーブルが存在しなかった場合にデータベースにtweetsテーブルを作るCREATE TABLE文が書かれています。テーブルの定義はここに書かれているわけです。

これでデータベースにツイートを格納する準備はできたので、タイムラインを取得する部分でツイートを保存するようにしてみましょう。

var db = new TweetDB();
twitterApi.statuses_home_timeline(
    {
        onSuccess: function(response){
            db.addTweets(response);
            updateTimeline(response);
        },
        onError: function(error){
            Ti.API.error(error);
        }
    }
);

まずTweetDBオブジェクを作成し自分のタイムラインを取得したときに、その内容をaddTweetsメソッドに渡しているだけですが、これでデータベースにツイートが保存できたはずです。

ツイートを取り出してみる

ツイートをデータベースに格納できるようになったので、表示する際にはデータベースからツイートを取得するようにします。こうすることで最新のツイートと過去のツイートを連続して表示することができます。

ツイートを取得するメソッドgetSavedTweetsをtweet_db.jsに実装します。

this.getSavedTweets = function() {
    this.open();
    var rows = this.db.execute( 'SELECT * FROM tweets ORDER BY created_at DESC' ); //(1)
    var res = [];
    if ( rows.getRowCount() > 0 ) {
        while ( rows.isValidRow() ) { //(2)
            var tweetObj = {};
            tweetObj.user = {};
            tweetObj.user.screen_name = rows.fieldByName('screen_name');
            tweetObj.user.profile_image_url
                = rows.fieldByName('profile_image_url');
            tweetObj.text = rows.fieldByName('tweet_text');
            var date = new Date(rows.fieldByName('created_at'));
            tweetObj.created_at = date.toLocaleString();
            res.push(tweetObj);
            rows.next();
        }
    }
    rows.close();
    this.close();
    return res;
};

(1)でSELECT文を発行し、保存されているツイートをすべて取得します。続いて、(2)のループで取得したデータを逐次、Twitter APIで返ってくるデータと同じ構造に変換しています。重要な点はループの一番最後のrows.next()メソッドで、このメソッドを実行することで、rowsが指しているデータの内容が次の行のものになります。次の行が存在しない場合、isValidRow()が偽値になるためループが終了します。また、Titanium.Database.ResultSetオブジェクトであるrowsはcloseすることで、それ以降結果を参照できなくなります。ツイート取得メソッドを実装したので、表示するtable_view.js側も変更します。

twitterApi.statuses_home_timeline(
    {
        onSuccess: function(response){
            db.addTweets(response);
<p>            updateTimeline(db.getSavedTweets()); //この行が変更されています</p>
        },
        onError: function(error){
            Ti.API.error(error);
        }
    }
);

タイムラインを取得した後にそのデータを保存し、データベースから改めてデータを取得するようにしています。

ツイートを検索してみる

今回はさらに、ツイートの簡単な検索も実装してみます。画面左上のsearchボタンをタップすると、検索窓が現れツイートをユーザー名で検索できるようにしてみます。

このような検索窓が表示されます
このような検索窓が表示されます

tweet_db.jsに今度は与えられた文字列を含むユーザーのツイートだけを返すようなメソッドを追加します。

this.searchByScreenName = function (screen_name) {
    this.open();
    var rows = this.db.execute(
        'SELECT * FROM tweets WHERE screen_name like ?',
        '%' + screen_name + '%'
    );
    var res = [];
    if ( rows.getRowCount() > 0 ) {
        while ( rows.isValidRow() ) {
            var tweetObj = {};
            tweetObj.user = {};
            tweetObj.user.screen_name = rows.fieldByName('screen_name');
            tweetObj.user.profile_image_url
                = rows.fieldByName('profile_image_url');
            tweetObj.text = rows.fieldByName('tweet_text');
            var date = new Date(rows.fieldByName('created_at'));
            tweetObj.created_at = date.toLocaleString();
            res.push(tweetObj);
            rows.next();
        }
    }
    rows.close();
    this.close();
    return res.length ? res : null;
;

getSavedTweetsメソッドとほとんど同じですが、引数にqueryが受け取れる点と、実行しているSQLにWHERE節がある点が異なります。SQLのWHERE節でscreen_nameをlike探索しているので、与えられた文字列がユーザー名中に含まれていると、そのユーザーのツイートが返るようになっています。

続いてtable_view.jsでの表示を変更します。

var search = Titanium.UI.createSearchBar({  //(1)
    height:43,
    top:0
});
win1.add(search);

search.addEventListener('return', function(e) //(2)
{
    var query = e.value;
    var res = db.searchByScreenName(query);
    if (res) {
        updateTimeline(res);
    }
    search.blur();
    search.hide();
});

search.addEventListener('change', function(e) //(3)
{
    if (e.value.length == 0 ) {
        updateTimeline(db.savedTweets());
        search.blur();
        search.hide();
    }
});
search.hide(); //(4)

var searchButton = Ti.UI.createButton( //(5)
    {
        title: 'search'
    }
);
searchButton.addEventListener( //(6)
    'click',
    function () {
        search.show();
    }
);
win1.leftNavButton = searchButton;

このコードでは、(1)の部分でSearchBarオブジェクトを作成し、(2)部分でSearchBarのなかで改行が押された時の処理を登録しています。この(2)の部分で先ほど作成したsearchByScreenName()メソッドを呼び出し、入力された文字列を名前に含むユーザーのツイートだけを表示するようにしています。

(3)の部分ではSearchBarの中の文字列が変化したときの処理を登録しており、SearchBarの中の文字列が無くなった時に、表示を検索結果の表示からすべてのツイートの表示に変更するようにしています。SearchBarが常に表示されていても邪魔なので(4)で表示を消し、(5)でSearchBarを表示するためのボタンを用意しています。そして、(6)でボタンが押されたときの処理として、SearchBarオブジェクトをshow()メソッドで表示するように実装しています。

まとめ

駆け足になりましたが、SQLを使ってツイートのデータを操作し、過去のツイートについても表示したり、検索したりできるような実装を解説しました。ツイートの例でもわかるように、検索を行ったり同じデータが重複しないように保存したりといったときにSQLはとても便利です。もし、Titaniumでアプリケーションを作成するときに、大量のデータを扱う必要が出てきた時にはSQLを利用することを一度考えてみてください。

おすすめ記事

記事・ニュース一覧