スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

AndroidのDialog表示(API13以降)

本日はAndroidのActivityでダイアログを表示する方法を紹介します。
API13以前は、ActivityのonCreateDialog()メソッドでDialog表示を行ったりしていましたが、どうやらAPI13以降ではdeprecatedの扱いになっているようなので、Googleさんがお勧めしているやり方でDialogを表示します。

1. DialogFragmentの実装
先ずはDialogの作成を担当するクラスとしてDialogFragmentを継承したサブクラスを実装します。
そしてonCreateDialog()メソッドをオーバライドして、その中でDialogを作成して返却するように実装します。

public class CustomDialogFragment extends DialogFragment {

@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setTitle(R.string.title_delete_all_history);
builder.setMessage(R.string.msg_delete_all_history)
.setPositiveButton(R.string.msg_yes, new DialogInterface.OnClickListener() {

@Override
public void onClick(DialogInterface dialog, int which) {
// ダイアログのOKボタンをクリックした時の処理
}
})
.setNegativeButton(R.string.msg_no, new DialogInterface.OnClickListener() {

@Override
public void onClick(DialogInterface dialog, int which) {
// ダイアログのCANCELボタンをクリックした時の処理
}
});
return builder.create();
}
}

ダイアログの作成は従来と変わらずAlertDialog.Builderで行います。
今回のケースだとsetPositiveButton()、setNegatiiveButton()の2つを設定しているので、OK/CANCELの2つのボタンが存在するダイアログを作成しています。
別クラスを作成する場合は、Activity(Context)をパラメータでもらってフィールドに保持するような作りをしたりしますが、DialogFragmentにはgetActivity()というメソッドが存在するのでフィールドに持つ必要はありません(ここは少し奇麗に書けるようになった部分かな)。
話が少しそれますが、このBuilderの実装は少し前にはやった流れるインターフェースというやつですね。今回は最後のbuilder.create()で一呼吸おいてますので若干流れが悪くなってますが。

2.ダイアログの表示
続いてダイアログを表示するコードです。
先ほど作成したDialogFragmentのサブクラスを生成して、show()メソッドを呼び出します。

public void showDialog() {
DialogFragment dialog = new CustomDialogFragment();
dialog.show(getFragmentManager(), "CustomDialogFragment");
}

show()メソッドの2番目の引数はダイアログを一意に特定する識別子で任意の文字列を設定できます。
ここに設定した文字列でfindFragmentByTag()メソッド等でダイアログを探す事が出来たりするようです。
getFragmentManager()でFragmentManagerのインスタンスを取得していますが、Android3.0より前のコードの場合はFragmentActivity#getSupportFragmentManager()メソッドでFragmentManagerを取得します。

ダイアログの表示だけなら、ここまでで実装完了です。
しかし、このままだとDialogのボタンをクリックした後の処理をDialogFragmentのサブクラスで実装しなければ行けなくなってしまいます。

そこでこの後は、ダイアログのボタンをクリックした後のコールバックをダイアログの表示元(ホスト)のActivityに返すための実装を追加します。

3.イベントリスナの定義
先ほど作成したDialogFragmentのサブクラスに内部インターフェースとしてイベントリスなを定義し、定義したインタフェースを自体をフィールドとして宣言します。
onCreateDialog()メソッドの中で作成したボタンに設定したイベントリスナ(OnClickListener)の中で、先ほど内部インターフェースとして宣言し、かつフィールドとして宣言したイベントリスナの適切なメソッドを呼び出します。
また、FragmenのライフサイクルイベントでコールバックされるonAttach()メソッドをオーバライドし、getActivity()で取得したActivity(すなわちダイアログの表示元)をイベントリスナの型にキャストしてフィールドに設定します。
ここまで、くれば次にやる事も大体分かりますね。


public class CustomDialogFragment extends DialogFragment {

// イベントリスナの定義
public interface NoticeDialogListener {
public void onDialogPositiveClick(DialogFragment dialog);
public void onDialogNegativeClick(DialogFragment dialog);
}

// イベントリスナをフィールドに宣言
NoticeDialogListener mListener;

@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setTitle(R.string.title_delete_all_history);
builder.setMessage(R.string.msg_delete_all_history)
.setPositiveButton(R.string.msg_yes, new DialogInterface.OnClickListener() {

@Override
public void onClick(DialogInterface dialog, int which) {
// イベントリスナのメソッド呼び出し
mListener.onDialogPositiveClick(DeleteHistoryDialogFragment.this);
}
})
.setNegativeButton(R.string.msg_no, new DialogInterface.OnClickListener() {

@Override
public void onClick(DialogInterface dialog, int which) {
// イベントリスナのメソッド呼び出し
mListener.onDialogNegativeClick(DeleteHistoryDialogFragment.this);
}
});
return builder.create();
}

@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
// キャストしてフィールドに設定
mListener = (NoticeDialogListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString()
+ " must implement NoticeDialogListener");
}
}
}


4.Activityにイベントリスナを実装
続いてActivityにイベントリスナを実装し、必要なメソッドをオーバライドします。
イベントリスナーを実装しないと、先ほどのDialogFragmentのサブクラスでオーバライドしたonAttach()メソッドでキャスト例外が発生します。
オーバライドしたonDialogPositiveClick()、onDialogNegativeClick()がダイアログのボタンを選択した時にコールバックされるので、これらのメソッドの中でボタンクリック後に行いたい処理を実装します。

public class ExampleActivity extends Activity
implements DeleteHistoryDialogFragment.NoticeDialogListener {

@Override
public void onDialogPositiveClick(DialogFragment dialog) {
// ダイアログのOKボタンをクリックした後に行いたい処理を実装
}

@Override
public void onDialogNegativeClick(DialogFragment dialog) {
// ダイアログのCANCELボタンをクリックした後に行いたい処理を実装
}
:

やっている事は自作でイベントリスナを実装するのと大して変わりないかと思います。

参考:
公式ドキュメント API Guides - Dialogs
公式ドキュメント API Guides - Fragments
スポンサーサイト

WindowsのサービスからはGUIが使えない

最近知ったのですが、Windows Vista以降からは、ユーザのセッションとサービスを起動しているセッションが異なるようです。
サービスが実行されているセッションを「セッション 0 」と呼び、それ以外はユーザのセッションとして、ユーザセッションのアプリからは、サービスにはアクセス出来なくなっています。

セッション 0 の分離

これの影響でサービスとして実行しているアプリケーションからGUIを利用(ウィンドウの起動とか)を実行した場合に、その結果をユーザの画面で受け取ることが出来なくなりました。
(ウィンドウを起動しているのがサービスのセッションで、ユーザのセッションとは異なるので、ウィンドウが表示されないという結果)

ちなみに何故このような事に気が付いたかというと、JenkinsでSeleniumを自動実行させた場合にSeleniumのWebDriver、すなわちウィンドウが表示されないという事象が発生し、調査したいたところ今回の仕様を知ることになりました。
Jenkinsがサービスとして起動していた為、WebDriverのウィンドウが表示されなかったようです。
(しかし、テストは無事に実行されていました(画面のキャプチャも可)。サービスのセッションで問題なく動作していた模様)
JenkinsをサービスとしてではなくWebアプリ(war)でインストールしてジョブを実行するとWebDriverのウィンドウを表示する事が出来ました。

サービスのままでも問題はなさそうなのですが、画面のプロパティへのアクセス等が不可になっているとの仕様もある為、私は念のため、JenkinsをWebアプリで実行するようにしました。

後、少し頑張ればサービスからもGUIが起動出来るみたいです。その方法は以下の記事にあるようです。
(私は特に試していません)

How to start a GUI process from service, under Windows Vista/7

以上

SeleniumとFireMobileSimulatorの連携

SeleniumのUIテストをFireFoxのFireMobileSimulatorが適用された状態で実行する方法を紹介します。
FireMobileSimulatorを適用する事でスマートフォンサイトのUIテストが実際の端末の画面表示に近い結果で行う事ができます。

■環境
Windows 7
Firefox バージョン26.0
Selenium バージョン2.39.0

■方法
FireMobileSimulator等の拡張機能の設定は、Firefoxのプロファイルに保存されます。
Seleniumで何の設定もせずにWebDriverを実行するとSelenium専用のプロファイルが作成される為、FireMobileSimulatorの設定引き継がれずにUIテストを実行してしまいます。
WebDriverに対して、実行時に使用するプロファイルを指定する事でFireMobileSimulatorの設定を引き継ぐ事ができます。

1.Selenium専用のプロファイル作成
Selenium専用のプロファイル設定を作成する事で、通常のブラウザ利用のプロファイルとUIテスト用のプロファイルを別の設定で行えるようにします。

Windowsのスタートボタン→プログラムとファイルの検索ボックスに「firefox.exe -p」と入力し、プロファイルマネージャを起動します。
(Windows XP の場合は、スタート ボタンをクリックし、ファイル名を指定して実行... を選択)

「新しいプロファイルを作成...」をクリックして、プロファイル名を入力してプロファイルを作成します。
今回は「Selenium」というプロファイル名で作成しました。
profile_manager.png

2.Firefoxの起動
先ほどのプロファイルマネージャからdefaultのプロファイルを選択して「Firefoxの起動」ボタンをクリックしてFirefoxを起動します。
(ちなみに、この時点で先ほど作成したSeleniumのプロファイルでFirefoxを起動するとインストールしたプラグイン等が存在しない初期状態でFirefoxが起動します。)

3.FireMobileSimulatorの設定
起動したFirefoxにてFireMobileSimulatorを有効にし、特定の端末を選択した状態で、適当なサイトを表示します。
(サイトの表示はFireMobileSimulatorが適用されたか確認する為)

4.WebDriverのプロファイル指定(default)
Firefoxの「ヘルプ」メニュー→「トラブルシューティング情報...」を選択します。
以下のような画面が表示されるのでプロファイルフォルダ項目の「フォルダを開く」ボタンをクリックするとdefaultプロファイルのフォルダがエクスプローラで表示されます。

troubleshooting.png

私のPC環境では以下のパスでした。

C:\Users\ユーザ名\AppData\Roaming\Mozilla\Firefox\Profiles\dy1zzs43.default


続いてテストケースのWebDriverにdefaultプロファイルを指定し、FireMobileSimulatorが適用された状態でUIテストが行えるか確認します。
以下のように、テストコードで先ほど取得したdefaultプロファイルのパスを設定します。

@Before
public void setUp() throws Exception {
File file = new File("C:/Users/ユーザ名/AppData/Roaming/Mozilla/Firefox/Profiles/\dy1zzs43.default");
FirefoxProfile profile = new FirefoxProfile(file);

driver = new FirefoxDriver(profile);
}

青色フォントの部分が追加したコードです。
Fileのインスタンスを指定しFirefoxProfileインスタンスを生成し、こちらをWebDriverのコンストラクタに指定する事でプロファイルの設定が行えます。

4.UIテスト実行(defaultプロファイル)
この状態で現在のFirefoxのプロファイル(すなわち、FireMobileSimulaorが適用された状態)でUIテストを実行する事ができるようになりました。
早速、Eclipse等からUIテストのテストケースを実行します。

テストケースが起動するブラウザにFireMobileSimulatorが適用されているか確認します。
たまに、プロファイルが更新されてなくて、FireMobileSimulatorが適用されていない場合があるので、その時は、再度、手動でFirefoxを起動してFireMobileSimulatorを適用した状態でタブを増やしたり、ブラウザを閉じたりしてみてください(そうすれば、Firefoxがプロファイルを保存してくれるはず)。

5.プロファイルのコピー
SeleniumのテストでFireMobileSimulatorが適用されているのが確認できたら、defaultプロファイルを最初に作成したSeleniumプロファイルにコピーします。

「C:\Users\ユーザ名\AppData\Roaming\Mozilla\Firefox\Profiles\dy1zzs43.default」フォルダ配下のファイルとフォルダを全てコピーし、Seleniumプロファイルのディレクトリ配下に全て上書きします。
Seleniumプロファイルのディレクトは、defaultプロファイルのディレクトリが存在する場所と同じ階層にあります。
フォルダ名が「xxxxx.Selenium」となっているはずです。

私のPC環境では以下のパスでした。

C:\Users\ユーザ名\AppData\Roaming\Mozilla\Firefox\Profiles\bkjbp0fr.Selenium

6.UIテスト実行(Seleniumプロファイル)
プロファイルのコピーが完了したら、テストコードを変更し、WebDriverのプロファイルの設定をSeleniumプロファイルに変更します。

@Before
public void setUp() throws Exception {
File file = new File("C:/Users/ユーザ名/AppData/Roaming/Mozilla/Firefox/Profiles/\bkjbp0fr.Selenium");
FirefoxProfile profile = new FirefoxProfile(file);
driver = new FirefoxDriver(profile);
}

テストコードを変更したら、再度、テストケースを実行します。

起動するブラウザにFireMobileSimulatorが適用されている事を確認してください。
適用できている事が確認できたら、手動でFirefoxを起動し、FireMobileSimulatorを解除してください。
そのあと、UIテストを実行してみてもFireMobileSimulatorが適用された状態でブラウザが起動するはずです。
(Seleniumプロファイルが有効になっている為)

手順は以上です。

■おまけ
ちなみにFireMobileSimulatorの設定が、プロファイルのどのファイルに保存されているのかを探したところ、プロファイルのフォルダ内にある「prefs.js」に保存されている事がわかりました。
FireMobileSimulatorが適用されている状態だとファイル内に以下のようなエントリが存在します。

user_pref("msim.current.carrier", "SB");
user_pref("msim.current.id", "1");

シミュレータで選択している端末とキャリアのエントリです。
数値の部分は、FireMobileSimulatorで何番目の端末を選択しているかを表しているようです。

もし、Seleniumの設定でうまくいかない場合は、こちらのファイルの内容も確認してみるとよいでしょう。

■参考
mozilla support - プロファイルの管理

Cypherでリコメンド

今回はNeo4jのCypherの使い方をリコメンドを例に紹介します。

■環境
Neo4j version 2.0 Community

■図1
rec01.png

上の図では、それぞれの購入アイテムが以下のような状況です。
ユーザと購入アイテムを「PURCHASED」というリレーションシップで関連付けています。

ユーザA
 ┗アイテム1
 ┗アイテム2
ユーザB
 ┗アイテム2
 ┗アイテム3
ユーザC
 ┗アイテム2
 ┗アイテム3
 ┗アイテム4

そして、今回はユーザBへリコメンドするアイテムをCypherで問合せます。
想定されえるリコメンド対象は以下の2つのアイテムです。

アイテム2にてリンクしているユーザAの購入アイテム → アイテム1
アイテム2,3にてリンクしているユーザCの購入アイテム → アイテム4

当然のことながら既にユーザBが購入済みのアイテム2、アイテム3はリコメンドの対象としません。

では、リコメンド対象を取得するCypherを以下に記述します。

match (user1{name:'ユーザB'})-[:PURCHASED]->(items)
<-[:PURCHASED]-(others)-[:PURCHASED]->(recommends)
return recommends

まず、(user1{name:'ユーザB'})-[:PURCHASED]->(items)の部分でユーザBの購入アイテムを表現しており、
その後の<-[:PURCHASED]-(others)で、ユーザBの購入商品と同じ商品を購入しているユーザ(others)を表現しています。
最後の(others)-[:PURCHASED]->(recommends)でリコメンド対象となるアイテムを取得しています。

しかし、実行結果を確認すると以下の5件が取得されています。

-----
アイテム1
アイテム3
アイテム4
アイテム2
アイテム4
-----

う~ん、何故でしょう?
既に購入済みのアイテム2、アイテム3が取得されています。
Neo4jには一意性の特性があり、既に辿ってきた関連性と同じものは検索しないようになっています。
それでは、一意性の特性がどのようなものか以下の図2を使って説明します。

■図2
friends.png

図2のケースで、Aさんの友達の友達、すなわちCさんを検索するCypherは以下となります。

MATCH (user{ name: 'Aさん' })-[r1:FRIEND]-()-[r2:FRIEND]-(friend_of_a_friend)
RETURN friend_of_a_friend

クエリの実行結果は以下の通りです。

-----
Cさん
-----

意識はしていませんが、実は既にこの時点で一意性の特性が有効になっています。
どう言う事か、上記のクエリを使って解説します。

まずは、(user{ name: 'Aさん' })-[r1:FRIEND]-()で、Bさんを取得(友達)。
次の()-[r2:FRIEND]-(friend_of_a_friend)でCさんを取得(友達の友達)。
しかし、よく考えてみてください。
問合せた内容は「友達の友達」です。そして、Bさん(友達)の友達は以下の2人となります。

Bさん→Aさん
Bさん→Cさん

そうです。ここでCさんだけ取得できるようになっているのは一意性が発揮されているのです。
では度々ですが、更に詳しく一意性とはどのようなものか、先ほどのクエリを使って解説します。
図3
friends2.png

最初のBさんを取得する部分の「MATCH (user{ name: 'Aさん' })-[r1:FRIEND]-()」で、r1にAさんとBさんの関連が格納されます。
そして「()-[r2:FRIEND]-(friend_of_a_friend)」でBさんとCさん(Bさんの友達)の関連が格納されます。
このr2を格納する時にBさんのもう一人の友達、すなわちAさんも格納されそうですが、一度辿ってきた関連のr1と同じものは、後のクエリでは格納されない動作をします。
これが一意性の特性です。
このような特性により、2つ以上の深さのある関連性(友達の友達等)の検索を行いやすくしています。

さて、一意性の特性があるなら、最初のリコメンドのクエリも購入済みのアイテムは取得しないのではないかと考えられます。
私が最初にイメージしたCypherの動作は下図のようなものでした。

図4 Cypher動作イメージ(誤解)
rec02.png

アイテム2、アイテム3はそれぞれリコメンド対象の検索過程で関連を既に辿っているので抽出はされてこないのではないかと。
しかし、そこには落とし穴がありまして、実際は下図のような動作をしています。

図5 Cyper動作イメージ(本来)
rec03.png

なんと、緑色と青色の別のパス経由で既に購入済みのアイテムが抽出されています。
一意性の特性は既に辿ったとこのある関連については、重複しないように動作するというものです。
言いかえれば、辿った事のない関連については取得します。
辿った事があるかどうかは、
「クエリの抽出対象となるノードを見つけるまでに経由したノードとリレーションと全く同じパス」
である事が条件となる為、今回のような購入済みのアイテムが抽出されてくるのです。

では、どのようにして購入済みのアイテムを抽出対象外とするのかというと、NOT関数とパターンを利用します。
Cypherのクエリは以下の通りです。

match (user1{name:'ユーザB'})-[:PURCHASED]->(items)
<-[:PURCHASED]-(others)-[:PURCHASED]->(recommends)
where not user1-[:PURCHASED]->(recommends)
return recommends

NOT関数はWHERE句の中で使用します。

not user1-[:PURCHASED]->(recommends)

実行結果
-----
アイテム1
アイテム4
アイテム4
-----

recommendsの中には抽出してきたアイテムが格納されており、こちらの中から既にユーザBがリレーション(PURCHASED)で関連しているノードと一致するものを否定するという指示になります。
これでやっと購入済みのアイテムをフィルタリングする事ができました。

ちなみに実行結果としてアイテム4が2件取得されていますが、この結果を応用すればアイテム毎のリコメンドの優先度(おすすめ度)を含めた実行結果を取得する事が出来ます(今回だとアイテム4 > アイテム1の優先度)。
こちらの方法については読者の方で考えてみてください。

■参考
Neo4jマニュアル - 7.4. Uniqueness
プロフィール

まこち

Author:まこち
スマートフォンのアプリ開発やWebサイト構築等を仕事や趣味でやっています。
最近はグラフデータベースも始めました。

最新記事
最新コメント
最新トラックバック
月別アーカイブ
カテゴリ
検索フォーム
RSSリンクの表示
リンク
ブロとも申請フォーム

この人とブロともになる

QRコード
QR
上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。