AdobeAIRでTwitterクライアントを作る 第3弾

AdobeAIRでTwitterクライアントを作る、第3弾。
今回はTimerを使って定期的にタイムラインを取得できるように改造しました。

またしてもソースコードをべたっと貼ります・・・というのは頭が悪いので、前回からの変更点を。


AdobeAIRでいわゆるタイマーを使うにはflash.utils.Timerクラスを使います。

例えば、1000ミリ秒ごとに動作するタイマーオブジェクトを作るには、以下のように書きます。

var delay:Timer = Timer(1000);

もちろん作っただけでは動作しません。

delay.start();

としてタイマーを動かします。


また、現状ではタイマーは動くだけで何もしませんが、イベントリスナを追加する事で指定された時間が経過するごとにイベントを発生させるようにすることができます。
例えば以下のようにdelayHandlerをイベントリスナに追加します。

delay.addEventListener(TimerEvent.TIMER,delayHandler);

この場合、呼び出されるメソッドは以下のように記述します。

// タイマーイベントのイベントハンドラ
public function delayHandler(evt:TimerEvent):void{

    trace("タイマーイベント");

}


上記の方法を利用して今回作成したプログラムの一部です。

/*
 * イニシャライザで
 * timerStart();
 * として呼び出しを行っている。
 */

// タイマースタート
public function timerStart():void{

    // タイマーを初期化 60秒ごとにイベントを発行
    delay = new Timer(60000);

    // delayHandlerをイベントリスナとしてdelay(タイマー)に追加
    delay.addEventListener(TimerEvent.TIMER,delayHandler);

    //タイマーをスタート
    delay.start();

}

// タイマーイベントのイベントハンドラ
public function delayHandler(evt:TimerEvent):void{

    //更新中である事をGUIに表示
    update.text = "更新中";

    //更新ボタンが押されたときの処理を実行(=タイムラインを取得しにいく)
    buttonClicked();
}

最後に今回のプログラムをのせておきます。参考にしてください。
とはいえTabとスペースがごちゃ混ぜになっててとても見づらい状況に。どうしよう。

以下、AIRTwitterClient.mxml

<?xml version="1.0" encoding="utf-8"?>
<mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" creationComplete="init()" width="454" height="300">
	<mx:Metadata>
    	//メタデータ。"onLoginSuccess"イベントを発行する可能性があることを示す
        [Event("onLoginSuccess")]
    </mx:Metadata>
	
	<mx:Script>
		<![CDATA[
			// 配列を利用する為のインポート
            import mx.collections.ArrayCollection;
            // ポップアップを利用する為のインポート
            import mx.managers.PopUpManager;
            // アラートを利用する為のインポート
            import mx.controls.Alert;
			
			// ログイン成功イベントを表す文字列
            public static const ON_LOGIN_SUCCESS:String = "onLoginSuccess";

            // ログインダイアログ
            private var loginDialog:LoginDialog;

			private var userName:String = "";
			private var password:String = "";
			
			// タイマー
			private var delay:Timer;
			
			// Twitter.comにアクセス中かどうか
			private var isAccess:Boolean = true;
			
			//RFC2822形式準拠の日時を生成する為に使う
			private var weekDayLabels:Array = new Array("Sun",
				"Mon",
            	"Tue",
            	"Wed",
            	"Thu",
            	"Fri",
            	"Sat");
			private var monthLabels:Array = new Array("Jan",
            	"Feb",
            	"Mar",
           		"Apr",
            	"May",
            	"Jun",
            	"Jul",
             	"Aug",
              	"Sep",
              	"Oct",
              	"Nov",
              	"Dec");
			private var date:Date;
			
            // データグリッドと連動するコレクション
            [Bindable]
            private var timeline:ArrayCollection = new ArrayCollection();

            // 初期化
            private function init():void {
                // ログインダイアログの作成
                loginDialog = PopUpManager.createPopUp(this, LoginDialog, true) as LoginDialog;
                // ログイン成功時のイベントリスナを追加
                loginDialog.addEventListener(LoginDialog.ON_LOGIN_SUCCESS, onLoginSuccess);
                // ダイアログを中央に表示
                PopUpManager.centerPopUp(loginDialog);
                
                // タイマー起動
                timerStart();
            }
            
            
            // ログイン成功時の処理
            private function onLoginSuccess(e:Event):void {
        		if(userName != ""){
        			// 初回呼び出しのときだけユーザー名とパスワードを取得
            		userName=loginDialog.userName.text;
            		password=loginDialog.password.text;
            		
          		}
          		
                // ポップアップを除去
                PopUpManager.removePopUp(loginDialog);

                // 入力されたユーザ名(ログイン失敗を考慮していない)
                var userName:String = loginDialog.userName.text;
                
                // リクエスト用文字列を生成
				var requestString:String = "http://twitter.com/statuses/friends_timeline/" + userName + ".xml";
				//var uv:URLVariables = new URLVariables();
				
                // URLRequestを用いてTwitterのデータを取得(直近20件に限定)
                var req:URLRequest = new URLRequest(requestString);
				/*
				// 時刻が以前更新されていれば、それをもとにする。
				if(date!=null){
					
					//前回から更新されたところだけを取得しようという部分。このせいでIOエラーになる。
					var since:String = "?since=" 
						+ weekDayLabels[date.getDay()] 
						+ ", " + date.getDate() + " " 
						+ monthLabels[date.getMonth()] + " " 
						+ date.getFullYear() + " " 
						+ date.getHours() + ":" 
						+ date.getMinutes() + ":" 
						+ date.getSeconds() + " GMT";
					
            		// @fixme 英単語の直後にくる文字列が欠落してしまう。 例) "twitterクライアント" → "twitter"
            		uv.decode(since);
					req.data = uv;
				}
				*/
				// 今のところタイムラインを全部書き換えている。
				timeline.removeAll();
				
				
                // 読み込みが完了した時刻を設定
                date = new Date();
				
                // URLLoaderで読み込み
                var loader:URLLoader = new URLLoader(req);
                
                // 読み込み完了時の処理
                loader.addEventListener(Event.COMPLETE, function(event:Event):void {
                	//XML処理
                    var responseXML:XML = new XML(loader.data);
                    var index:int = 0;
                    //javaの拡張for文。responseXML.children()がなくなるまで、子をstatusに代入して回す。20回回るはず。
                    for each (var status:XML in responseXML.children()) {
                    	
                    	//タイムラインにアイテムを追加
                        timeline.addItem({
                        	//画像。urlを指定してダウンロードしている。
                            image: status.user.profile_image_url,
                            //名前。xml文書が持つ要素。
                            name: status.user.name,
                            //テキスト。ここでは発言の内容。これもxml文書に埋め込まれている。
                            text: status.text
                		}//,
                		//index
                		);
                		//index++;
                    }
                });
                
                update.text = "";
                isAccess = false;
				
            }
            
            
            // 更新ボタンが押されたときの反応
            private function buttonClicked():void {
            	
            	if(!isAccess){
	            	isAccess = true;
	            	// 自分自身にリスナを登録。
	            	this.addEventListener(LoginDialog.ON_LOGIN_SUCCESS, onLoginSuccess);
	            	
	            	// テキストフィールドが空か、そうでないか
	            	if(speak.text == ""){
	            		sendMessage("");
	            	}else{
	            		var sendmessage:String = speak.text;
	            		speak.text = "";
	            		
	            		sendMessage(sendmessage);
	            	}
            	}else{
            		update.text = "";
            	}
            }
            
            // 発言を登録する
            private function sendMessage(message:String):void {
            	
            	// 以降のURLRequestが全て認証情報付きで行われるように、デフォルト値としてセット
            	URLRequestDefaults.setLoginCredentialsForHost("twitter.com", userName, password);
				
            	// 以降、TwitterにHTTPリクエストを送信する処理
            	var req:URLRequest = new URLRequest("http://twitter.com/statuses/update.xml");
            	// POSTメソッドでなければいけない
            	req.method = "POST";
				
				// URIエンコードする
            	//if(message.length > 0){
            		var uv:URLVariables = new URLVariables();
            		
            		// @fixme 英単語の直後にくる文字列が欠落してしまう。 例) "twitterクライアント" → "twitter"
            		uv.decode("status="+message);
            		req.data = uv;
            	//}
            	
            	// HTTPリクエストを生成。
            	var loader:URLLoader = new URLLoader();
			
            	// HTTPレスポンスイベントを捕捉
            	loader.addEventListener(HTTPStatusEvent.HTTP_RESPONSE_STATUS, onHTTPResponse);
            
            	// HTTPリクエストを実行。
            	loader.load(req);
            	
            }
            
            // twitter.comから応答が得られたときの動作
            private function onHTTPResponse(event:HTTPStatusEvent):void {
        	
            	// HTTPレスポンスステータスが401(Unauthorized)だった場合、アラートを表示
            	if (event.status == 401 || event.status == 400) {
            	    Alert.show("Error:" + event.status, "エラー");
            	    isAccess = false;
            	    update.text = "";
            	    return;
            	}else if(event.status != 200){
            		Alert.show("Status:"+event.status,"HTTPStatus");
            	}
            	
            	// イベントを送出
            	dispatchEvent(new Event(ON_LOGIN_SUCCESS));
            
            }
            
            // タイマー
            public function timerStart():void{
            	// タイマーを初期化 60秒ごとにイベントを発行
				delay = new Timer(60000);
				// delayHandlerをイベントリスナとしてdelay(タイマー)に追加
				delay.addEventListener(TimerEvent.TIMER,delayHandler);
				delay.start();
			}
			
			// タイマーイベントのイベントハンドラ
			public function delayHandler(evt:TimerEvent):void{
				//trace("タイマーイベント");
				
				update.text = "更新中";
				buttonClicked();
				
			}
        
        ]]>
        <!--スクリプトの終端-->
    </mx:Script>
        
    <!--データグリッド。データを格子状に並べる。名前は"timeline"-->
    <mx:DataGrid dataProvider="{timeline}" right="0" bottom="61" top="-1" left="0">
        <!--列の成分を指定-->
        <mx:columns>
    		<!--イメージのレンダリング先を指定-->
            <mx:DataGridColumn headerText="画像" dataField="image" width="40">
                <mx:itemRenderer>
                    <mx:Component><mx:Image width="32" height="32"/></mx:Component>
                </mx:itemRenderer>
            </mx:DataGridColumn>
            <!--ユーザ名-->
            <mx:DataGridColumn headerText="ユーザ名" dataField="name" width="120"/>
            <!--コメント(発言)-->
            <mx:DataGridColumn headerText="コメント" dataField="text" wordWrap="true"/>
    	</mx:columns>
           	
    </mx:DataGrid>
    
    <mx:TextArea height="33" wordWrap="true" editable="true" bottom="19" left="0" right="54" id="speak"/>
    <mx:Button label="更新" right="0" bottom="31" click="buttonClicked();"/>
    <mx:Label width="46" bottom="10" right="0" id="update" />
    
</mx:WindowedApplication>