SQLの窓

2016年12月05日


Android Studio で Twitter4J で ListView に検索結果を表示する



Twitter4J

非同期処理は、常に TwitterListener を作成して、AsyncTwitter からの実行を リスナー側で受けるという形になります。( 今回の場合、searched(QueryResult queryResult) で受けて、search(Query query) で呼び出す )

関連する記事

Android Studio で Twitter4J で画像付きツイート

注意する事
1) トークンの保存場所が、app\src\main\resources の twitter4j.properties

2) TwitterListener で受けたところから画面にアクセスするには、UI スレッドに対する考慮が必要
   ( runOnUiThread メソッドが簡単 )

3) ライブラリは twitter4j-core-4.0.4.jar と twitter4j-async-4.0.4.jar をコピーして core のみ Gradle に登録
MainActivity
public class MainActivity extends AppCompatActivity {

    // リストビュー
    private MyArrayAdapter adapter;
    private ListView listview;

    // 日付データ用
    private Calendar cal;
    private SimpleDateFormat sf;

    // Twitter 用
    private TwitterListener listener;
    private AsyncTwitter asyncTwitter;
    private ArrayList<TwitterData> twitterData;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);


        // アダプタを作成
        adapter = new MyArrayAdapter(MainActivity.this,R.layout.list_item);

        // リストビューの取得
        listview = (ListView) MainActivity.this.findViewById(R.id.listView);
        // リストビューにデータを表示
        listview.setAdapter(adapter);

        Button buttonSearch = (Button) MainActivity.this.findViewById(R.id.buttonSearch);
        buttonSearch.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                adapter.clear();
                loadDataList();
            }
        });

        cal = Calendar.getInstance();
        sf = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
        // リスナーを作成する
        listener = new TwitterAdapter() {
            @Override
            public void searched(QueryResult queryResult) {

                twitterData = new ArrayList<TwitterData>();
                List<Status> list = queryResult.getTweets();

                Iterator<Status> it = list.iterator();
                while( it.hasNext() ) {

                    Status statudData = it.next();

                    twitterData.add(new TwitterData(
                        // ScreenName と Name
                        String.format("%s : %s", statudData.getUser().getScreenName(), statudData.getUser().getName()),
                        sf.format(statudData.getCreatedAt()),
                        statudData.getText()));

                }

                // UI スレッドで アダプタにデータをセット
                MainActivity.this.runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        adapter.addAll(twitterData);
                    }
                });


            }

            @Override
            public void onException(TwitterException te, TwitterMethod method) {

                Log.i("lightbox", "onException");
                te.printStackTrace();

            }

        };

        // リスナーを登録する
        AsyncTwitterFactory factory = new AsyncTwitterFactory();
        asyncTwitter = factory.getInstance();
        asyncTwitter.addListener(listener);

    }

    // *****************************************
    // Twitter から リストビューにデータを表示
    // *****************************************
    public void loadDataList() {

        EditText editSearch = (EditText) MainActivity.this.findViewById(R.id.editSearch);
        String searchString = editSearch.getText().toString();

        Query query = new Query(searchString);
        asyncTwitter.search(query);


    }
}


MyArrayAdapter
public class MyArrayAdapter extends ArrayAdapter<TwitterData> {

	// コンストラクタで渡された画面の保存
	private int mResource;
	// 画面作成用
	LayoutInflater mInflater;

	public MyArrayAdapter(Context context, int resource) {
		super(context, resource);
		// ArrayAdapter でも、このようにして保存して利用してます
		mResource = resource;
		mInflater = LayoutInflater.from(context);
	}

	@Override
	public View getView(int position, View convertView, ViewGroup parent) {

		if (convertView == null) {
			convertView = mInflater.inflate(mResource, parent, false);
		}

		// アダプターより行データを取得
		TwitterData twitterData = MyArrayAdapter.this.getItem(position);

		// 画面にデータをセット
		TextView tv;

		// キー
		tv = (TextView) convertView.findViewById(R.id.textKey);
		tv.setText(twitterData.getScreenName());

		tv	= (TextView) convertView.findViewById(R.id.textItem1);
		tv.setText(twitterData.getStringDate());

		tv = (TextView) convertView.findViewById(R.id.textItem2);
		tv.setText(twitterData.getText());

		// 行の画面をシステムに返す
		return convertView;
	}

}


TwitterData
public class TwitterData {

	private String stringDate;
	private String screenName;
	private String text;

	public TwitterData(String screenName, String stringDate, String text) {
		this.screenName = screenName;
		this.stringDate = stringDate;
		this.text = text;
	}

	public String getScreenName() {
		return screenName;
	}

	public void setScreenName(String screenName) {
		this.screenName = screenName;
	}

	public String getStringDate() {
		return stringDate;
	}

	public void setStringDate(String stringDate) {
		this.stringDate = stringDate;
	}

	public String getText() {
		return text;
	}

	public void setText(String text) {
		this.text = text;
	}
}




posted by lightbox at 2016-12-05 19:16 | Comment(0) | Android Studio 2016 | このブログの読者になる | 更新情報をチェックする

2016年11月29日


Android Studio で Twitter4J で画像付きツイート

Twitter API は使用回数制限があるので、現在それほど魅力的なものではありませんが、BOT 的な使い道や、ログとして使うのならば、Java のサンプルとしては魅力的ではあると思います。特に、Twitter4J を使うと、いとも簡単にツイートは可能なのです。

ダウンロードしてインストール

ダウンロードリンクは Twitter4J にあります。解凍して Android Studio のプロジェクトの libs に twitter4j-core-4.0.4.jar と twitter4j-async-4.0.4.jar をコピーします。

この後、メニューから add as library でもかまいませんが、app の gradle が以下のようになればいいです
dependencies {
    compile fileTree(include: ['*.jar'], dir: 'libs')
    testCompile 'junit:junit:4.12'
    compile 'com.android.support:appcompat-v7:22.2.1'
    compile files('libs/twitter4j-core-4.0.4.jar')

}

twitter4j-async-4.0.4.jar は非同期処理に必要ですが、gradle に記述する必要はありません。

ちなみに、他のライブラリを全てコピーするとエラーになります(たぶん内容が重複してるとかかもしれません)

設定ファイル

twitter4j.properties が必要です。場所は app\src\main\resources です。

エクスプローラから作成してテキストファイルを作成して、自分の Twitter から、右下の『開発』をクリックして、My Apps で アプリを作るか既存のアプリからトークンをコピペして twitter4j.properties に書き込むだけです

このへんは、Twitter の API を使った人なら常識の範疇ですが、初めてでもインターネットでたくさん紹介されているので問題は無いはずです。
debug=true
oauth.consumerKey=*********************
oauth.consumerSecret=******************************************
oauth.accessToken=**************************************************
oauth.accessTokenSecret=******************************************

実行コード

画像は、あらかじめ用意しておいてパスを取得しておきます。カメラを使用するなら、ギャラリーに登録までした後のパスをそのまま使えばいいし、一時的に使うだけなら後から削除でいいですね(もちろん終了イベント後に削除)

カメラを起点とした画像データの処理 / Android

配布ページのサンプルコードは、タイプミスがあるので注意

▼ 実行
				// 入力テキストを取得
				EditText editText = (EditText) rootView.findViewById(R.id.tweetText);
				String text = editText.getText().toString();

				// 入力なしなら、テキストを作成
				if ( text.trim().equals("") ) {
					Calendar cal = Calendar.getInstance();
					SimpleDateFormat sf = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
					text = String.format( "%s %s", sf.format(cal.getTime()), "に Android からツイートしました");
				}
				// デバッグ用
				Log.i("lightbox",text);

				// 非同期のリスナーを作成
				TwitterListener listener = new TwitterAdapter() {
					@Override
					public void updatedStatus(Status status) {
						System.out.println("Successfully updated the status to [" +
							status.getText() + "].");
					}

					@Override
					public void onException(TwitterException te, TwitterMethod method) {
						if (method == TwitterMethod.UPDATE_STATUS) {
							te.printStackTrace();
						} else {
							throw new AssertionError("Should not happen");
						}
					}

				};

				// 非同期の準備
				AsyncTwitterFactory factory = new AsyncTwitterFactory();
				AsyncTwitter asyncTwitter = factory.getInstance();
				// リスナーを登録
				asyncTwitter.addListener(listener);

				// StatusUpdate ノインスタンスを作成
				// ※ 画像ツイートに必要
				StatusUpdate statusUpdate = new StatusUpdate(text);
				// 画像のファイルオブジェクト
				File file = new File( mainActivity.getimagePath() );
				// 画像を設定
				statusUpdate.media(file);

				// ツイート
				asyncTwitter.updateStatus(statusUpdate);


何故か非同期のサンプルが世の中に無かったですが、Android でツイートしようなんて思う人がたぶんもういないのでしょう( 殆どメリットや魅力が無いので )

updatedStatus イベント内の注意

この中は UI スレッドでは無いようなので、成功の後画面処理する場合は、適宜必要な UI スレッド呼び出しを実装します。

runOnUiThread( android.app.Activity )
UIスレッドで指定されたアクションを実行します。 現在のスレッドがUIスレッドの場合、アクションは直ちに実行されます。 現在のスレッドがUIスレッドでない場合、アクションはUIスレッドのイベントキューにポストされます。
posted by lightbox at 2016-11-29 20:10 | Comment(0) | Android Studio 2016 | このブログの読者になる | 更新情報をチェックする

2016年11月08日


内部ストレージを起点とした画像データの処理 / Android


Uri => File => FileInputStream => Bitmap というルートもあります

ストレージから画像を ImageView に表示する

※ ファイル名やフォルダは既知である事を前提としています。
// *************************************
// パスの取得
// *************************************
String path = String.format(
	"%s/firebase/20161108_011922.jpg",
	Environment.getExternalStorageDirectory().getPath()
);

// *************************************
// ImageView
// *************************************
Bitmap image = null;
FileInputStream fis;
try {
	fis	= new FileInputStream(path);
	image = BitmapFactory.decodeStream(fis);
} catch (Exception e) {
	e.printStackTrace();
}

// 画像データが取得されていたら
if ( image != null ) {
	// imageView へ表示
	imageView.setImageBitmap(image);
}

パスから FileInputStream を作成して、BitmapFactory.decodeStream でビットマップに変換します。画像表示は ImageView の setImageBitmap です。

Bitmap を使用した画像の回転
// バイト配列を Bitmap に変換( 内容は既に jpeg )
Bitmap image1 = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
// 90度回転
int width = image1.getWidth();
int height = image1.getHeight();
Matrix matrix = new Matrix();
matrix.postRotate (90);
Bitmap image2 = Bitmap.createBitmap (image1, 0, 0, width, height, matrix, true);
(※参考) ImageView を使用して画像を回転

ギャラリーから画像を ImageView に表示する

ギャラリーから返されるのは、Uri です。Uri を直接使用する setImageURI を使用します。

setImageURI(null) は、キャッシュされないようにする効果が期待できると想像しています。
// ギャラリーの呼び出し
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("image/*");
startActivityForResult(intent, 10);		// 10 は任意

// -------------------------------

// ギャラリーからの戻り
// ※ onActivityResult は MainActivity のメソッドです
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
	super.onActivityResult(requestCode, resultCode, data);

	if (requestCode == 10 && resultCode == Activity.RESULT_OK) {
		if (data != null) {
			Uri uri = data.getData();
			imageView.setImageURI(null);
			imageView.setImageURI(uri);
		}

	}
}

(※参考) Exif情報を利用して、画像を回転

Firebase の putFile と putStream

どちらも大差はありません。アップロード前に加工が必要なら、putStream になるかもしれませんし、コードを簡潔にするには、putFile だと例外処理がありませんから、キャッシュに書き込んでからアップロードしてもいいかもしれません。
// *************************************
// FireBase putFile
// *************************************
Uri file = Uri.fromFile(new File(path));
imageRef = storageRef.child(file.getLastPathSegment());
UploadTask uploadTask = imageRef.putFile(file);
uploadTask.addOnSuccessListener(new OnSuccessListener<UploadTask.TaskSnapshot>() {
	@Override
	public void onSuccess(UploadTask.TaskSnapshot taskSnapshot) {

		Log.i("lightbox","アップロードに成功しました");
		long size = taskSnapshot.getMetadata().getSizeBytes();
		Log.i("lightbox",String.format("サイズ : %d",size));

	}
}).addOnFailureListener(new OnFailureListener() {
	@Override
	public void onFailure(@NonNull Exception e) {

		Log.i("lightbox","アップロードに失敗しました");
	}
});


// *************************************
// FireBase putStream
// *************************************
Uri file = Uri.fromFile(new File(path));
imageRef = storageRef.child(file.getLastPathSegment());
InputStream stream = null;
try {
	stream = new FileInputStream(new File(path));
} catch (Exception e) {
	e.printStackTrace();
}
if ( stream != null ) {
	UploadTask uploadTask = imageRef.putStream(stream);
	uploadTask.addOnSuccessListener(new OnSuccessListener<UploadTask.TaskSnapshot>() {
		@Override
		public void onSuccess(UploadTask.TaskSnapshot taskSnapshot) {

			Log.i("lightbox", "アップロードに成功しました");
			long size = taskSnapshot.getMetadata().getSizeBytes();
			Log.i("lightbox", String.format("サイズ : %d", size));

		}
	}).addOnFailureListener(new OnFailureListener() {
		@Override
		public void onFailure(@NonNull Exception e) {

			Log.i("lightbox", "アップロードに失敗しました");
		}
	});
}




posted by lightbox at 2016-11-08 13:54 | Comment(0) | Android Studio 2016 | このブログの読者になる | 更新情報をチェックする

2016年11月07日


カメラを起点とした画像データの処理 / Android

全てのソースコードはこちら
▼ テスト用のカメラ処理はこちら
Android : 画像関連のテスト用カメラアプリ




byte 配列を Bitmap に変換して ImageView に表示する

1) BitmapFactory.decodeByteArray => bitmap
2) imageView.setImageBitmap(bitmap);
// バイト配列を Bitmap に変換( 内容は既に jpeg )
Bitmap image1 = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
// 90度回転
int width = image1.getWidth();
int height = image1.getHeight();
Matrix matrix = new Matrix();
matrix.postRotate (90);
Bitmap image2 = Bitmap.createBitmap (image1, 0, 0, width, height, matrix, true);

// 環境依存(これが無いと落ちるデバイスもある)
imageView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);

// imageView へ表示
imageView.setImageBitmap(image2);


byte 配列を内部ストレージに保存

byte[] data を FileOutputStream で write
// 内部ストレージに保存
FileOutputStream jpg;
try {
	jpg = new FileOutputStream(imagePath);
	jpg.write(data);
	jpg.close();

	// ギャラリーに反映
	MediaScannerConnection.scanFile(
		MainActivity.this,
		new String[]{imagePath},
		new String[]{"image/jpg"},
		null);

} catch (Exception e) {
	e.printStackTrace();
}

保存用の内部ストレージのパスの取得
	// *************************************
	// 保存用内部ストレージパス取得
	// *************************************
	private String buildPath(String folder, String ext) {

		String path = null;

		// 内部ストレージにフォルダを作成
		String TargetRootDir
			= String.format("%s/%s",Environment.getExternalStorageDirectory().getPath() , folder );
		File file = new File(TargetRootDir);
		// ディレクトリ初期作成
		if (!file.exists()) {
			if (file.mkdir() == false) {
				Log.i("lightbox", "ディレクトリを作成できませんでした");
				return path;
			}
		}

		// 画像保存パス
		Calendar cal = Calendar.getInstance();
		SimpleDateFormat sf = new SimpleDateFormat("yyyyMMdd_HHmmss");
		path = String.format("%s/%s.%s", TargetRootDir,sf.format(cal.getTime()),ext);

		Log.i("lightbox",path);

		return path;

	}


Firebase よりbyte 配列で画像を取得する

本来は、File か ストリームで取得しますが(メモリ効率)、カメラの画像が byte 配列なので、putBytes でそのままアップロードが可能です。なので、その反対である getBytes のサンプルです

putBytes のサンプルはこちら

Stream でダウンロードして ImageView に表示する
ファイルとしてダウンロードしてギャラリーに保存する
FirebaseStorage storage;
StorageReference storageRef;
StorageReference imageRef;

storage = FirebaseStorage.getInstance();
storageRef = storage.getReferenceFromUrl("gs://freebase-654b7.appspot.com/image");

imageRef = storageRef.child("sworc.png");
// ダウンロード
imageRef.getBytes(MainActivity.MAX_SIZE_BYTE)
	.addOnSuccessListener(new OnSuccessListener<byte[]>() {
		@Override
		public void onSuccess(byte[] bytes) {
			if (bytes != null) {
				Bitmap image = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
				imageView.setImageBitmap(image);
			}
		}
	}).addOnFailureListener(new OnFailureListener() {
	@Override
	public void onFailure(@NonNull Exception e) {
		Log.i("lightbox","データ取得に失敗しました");
		e.printStackTrace();
		Toast.makeText(
			MainActivity.this,
			"Firebase からのダウンロードに失敗しました",
			Toast.LENGTH_LONG)
			.show();
	}
});


関連する資料

ImageView を起点とした画像データの処理 / Android




posted by lightbox at 2016-11-07 22:25 | Comment(0) | Android Studio 2016 | このブログの読者になる | 更新情報をチェックする

2016年11月03日


バグ : Android Studio 2.2.2 で、spinnerMode を dialog に設定すると app:spinnerMode となってしまいます

通常は、エラーになるので気づくと思いますが、android:spinnerModeが正解です。ただ、DataBinding を使用すると(layout 要素内)、app: でカスタムセッターの名称を指定するのでエラーにならないようなのです。

気づかずにいると、当然ダイアログ表示がされないので注意して下さい。

設定すると



通常はエラー



Data Binding 時



エラーにならないので気が付かない・・・

app:syozokuData="@{jdata.syozoku}" は以下の定義で正しく動作します

▼ Data Binding の @BindingAdapter によるカスタム Setter の定義
	@BindingAdapter("syozokuData")
	public static void setSyozokuData(Spinner spinnerSyozoku, String syozoku) {
		if ( syozoku != null && !syozoku.equals("")) {
			ArrayAdapter adapter = (ArrayAdapter) spinnerSyozoku.getAdapter();
			int count = adapter.getCount();
			for( int i = 0; i < count; i++ ) {
				KeyValue kv = (KeyValue) adapter.getItem(i);
				if ( kv.getCode().equals(syozoku)) {
					spinnerSyozoku.setSelection(i);
					break;
				}
			}
		}
	}




タグ:トラブル
posted by lightbox at 2016-11-03 01:17 | Comment(0) | Android Studio 2016 | このブログの読者になる | 更新情報をチェックする

2016年11月01日


ListView + カスタム ArrayAdapter + ViewSwitcher + Firebase API + Data Binding : 更新処理 / Android Studio

Firebase の環境とプロジェクトの作成方法

シンプル Android Data Binding : Android Studio 2.2

tools.jar

画面定義
MainActivity 主な処理は、初期画面に表示する ListView の処理です。ListView 用の一覧データは、Firebase より取得します。API を使用して(orderByChild メソッド) でフリガナでソートしています。 また、初期画面には表示されていませんが、ViewSwitcher を使用しているので、次画面用の処理として NextPage クラスを用意して、ここで初期化しています。 特に、Spinner 用データ(性別・所属)も Firebase より取得し、KeyValue という汎用クラスを用いて処理を構築しています。(この参照用のデータの取得が成功してから、一覧データを取得しています) ListView 用の MyArrayAdapter は、Data Binding を使用しています
public class MainActivity extends AppCompatActivity {

	// ViewSwitcher
	public static int FIRST_PAGE = 0;
	public static int NEXT_PAGE = 1;

	// Data Binding
	private ActivityMainBinding binding;

	// Firebase
	private FirebaseDatabase database;
	private DatabaseReference mDatabase;

	// データフォーマット
	private JsonData json;

	// ローディング中のダイアログ
	private ProgressDialog progress;

	// 画面関連
	private ViewSwitcher vs;
	private NextPage nextPage;
	private ListView listview;
	private MyArrayAdapter adapter;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);

		// Data Binding を使用した画面表示
		binding = DataBindingUtil.setContentView(this, R.layout.activity_main);

		// Firebase Database 用
		database = FirebaseDatabase.getInstance();
		mDatabase = database.getReference();

		// ローディング中のダイアログ
		progress = new ProgressDialog(MainActivity.this);

		// ViewSwitcher で複数画面処理
		vs = (ViewSwitcher) MainActivity.this.findViewById(R.id.viewSwitcher);
		// 次画面処理
		nextPage = new NextPage(MainActivity.this);
		// 次画面の初期処理( イベント登録等 )
		nextPage.initAction();

		// リストビューの取得
		listview = (ListView) MainActivity.this.findViewById(R.id.listView);
		// リストビューの行をタップした時の処理
		listview.setOnItemClickListener(new AdapterView.OnItemClickListener() {
			@Override
			public void onItemClick(AdapterView<?> parent, View view, int position, long id) {

				// アダプターを取得( ここ専用 )
				MyArrayAdapter adapter = (MyArrayAdapter)parent.getAdapter();
				// 行データを取得
				json = (JsonData)adapter.getItem(position);
				// この行データを Data Binding で画面にセット
				binding.setJdata( json );
				// この行データを次画面で使用
				nextPage.setData( json );

				// 画面移動
				vs.setDisplayedChild(MainActivity.NEXT_PAGE);
			}
		});

		// ListView 用の アダブターを作成
		adapter = new MyArrayAdapter(MainActivity.this,R.layout.list_item);

		// 最新の Firebase のデータを取得する
		loadData();

	}

	// ******************************
	// データロード用の public メソッド
	// ******************************
	public void loadData() {
		// データロード前に表示
		progress.setProgressStyle(ProgressDialog.STYLE_SPINNER);
		progress.setMessage("データをロードしています");
		progress.show();

		// Firebase の item ツリーを name でソートして取得
		mDatabase.child("refdata")
			.addListenerForSingleValueEvent(new ValueEventListener() {
				@Override
				public void onDataChange(DataSnapshot dataSnapshot) {
					if ( dataSnapshot.exists() ) {

						// 性別コンボボックスの作成
						DataSnapshot ref1 = dataSnapshot.child("sex");
						GenericTypeIndicator<ArrayList<String>> t1 = new GenericTypeIndicator<ArrayList<String>>() {};
						ArrayList<String> sexData = ref1.getValue(t1);
						ArrayAdapter adapter = new ArrayAdapter(
							MainActivity.this,
							android.R.layout.simple_spinner_item);
						Spinner spinner = (Spinner) MainActivity.this.findViewById(R.id.spinnerSex);
						adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
						adapter.addAll(sexData);
						spinner.setAdapter(adapter);

						// 所属コンボボックスの作成
						DataSnapshot ref2 = dataSnapshot.child("syozoku");
						Iterator<DataSnapshot> child = ref2.getChildren().iterator();
						ArrayList<KeyValue> syozokuData = new ArrayList<KeyValue>();
						while(child.hasNext()) {
							DataSnapshot next = child.next();
							// Firebase API でJsonData に変換
							KeyValue kv = new KeyValue(next.getKey(),next.getValue().toString());
							// ArrayList に追加
							syozokuData.add(kv);
						}
						ArrayAdapter adapter2 = new ArrayAdapter(
							MainActivity.this,
							android.R.layout.simple_spinner_item);
						Spinner spinner2 = (Spinner) MainActivity.this.findViewById(R.id.spinnerSyozoku);
						adapter2.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
						adapter2.addAll(syozokuData);
						spinner2.setAdapter(adapter2);

						loadDataList();
					}
					else {
						Log.i("lightbox","コンボボックス用データを読み込めませんでした");

						// ローディングダイアログを閉じる
						progress.dismiss();
					}

				}

				@Override
				public void onCancelled(DatabaseError databaseError) {
					databaseError.toException().printStackTrace();
					Log.i("lightbox","StackTrace(コンボボックス用データ) を出力しました");

					// ローディングダイアログを閉じる
					progress.dismiss();
				}
			});


	}

	public void loadDataList() {

		// Firebase の class ツリーを furi でソートして取得
		mDatabase.child("class").orderByChild("furi")
			.addListenerForSingleValueEvent(new ValueEventListener() {
				@Override
				public void onDataChange(DataSnapshot dataSnapshot) {
					if ( dataSnapshot.exists() ) {

						// 空の ArrayList
						ArrayList<JsonData> listData = new ArrayList<JsonData>();

						// name でソートされたデータを dataSnapshot より取得
						Iterator<DataSnapshot> child = dataSnapshot.getChildren().iterator();
						while(child.hasNext()) {
							DataSnapshot next = child.next();
							// Firebase API でJsonData に変換
							json = next.getValue(JsonData.class);

							// ArrayList に追加
							listData.add(json);
						}

						// アダプタをクリア
						adapter.clear();
						// アダプタにデータセット ( listData は name でソートされた ArrayList )
						adapter.addAll(listData);
						// リストビューにデータを表示
						listview.setAdapter(adapter);

					}
					else {
						Log.i("lightbox","データを読み込めませんでした");

					}

					// ローディングダイアログを閉じる
					progress.dismiss();
				}

				@Override
				public void onCancelled(DatabaseError databaseError) {
					databaseError.toException().printStackTrace();
					Log.i("lightbox","StackTrace を出力しました");

					// ローディングダイアログを閉じる
					progress.dismiss();
				}
			});
	}
}


NextPage クラス

ViewSwitcher の中に include で用意した次画面の処理を行います。MainActivity の中での処理になりますが、別クラスで分離して管理するようにしています。

この中では、Firebase の API を使用して更新処理を行っています。Firebase の更新は、JsonData クラスに必要なデータをセットし、そのまま API へ渡すというとても簡単で解り易い仕様となっています。
public class NextPage {

	private MainActivity mainActivity;
	private ViewSwitcher vs;
	private JsonData json;
	private View include1;

	// Firebase
	private FirebaseDatabase database;
	private DatabaseReference mDatabase;

	// コンストラクタ
	public NextPage(MainActivity mainActivity) {
		this.mainActivity = mainActivity;
	}

	// 処理する行データが外部からセットされる
	public void setData( JsonData json )  {
		this.json = json;
	}

	// 初期処理
	public void initAction(){

		// ViewSwitcher( 複数画面処理 )
		vs = (ViewSwitcher) mainActivity.findViewById(R.id.viewSwitcher);

		// activity_next.xml の 親 view を取得
		include1 = mainActivity.findViewById(R.id.include1);

		// Firebase Database 用
		database = FirebaseDatabase.getInstance();
		mDatabase = database.getReference();

		// *****************************************
		// 戻るボタンの処理 ( include1 より取得 )
		// *****************************************
		Button backButton = (Button)include1.findViewById(R.id.backButton);
		backButton.setOnClickListener(new View.OnClickListener() {
			@Override
			public void onClick(View v) {

				// 最初の画面へ移動
				vs.setDisplayedChild(MainActivity.FIRST_PAGE);

			}

		});

		// *****************************************
		// 更新ボタンの処理 ( include1 より取得 )
		// *****************************************
		Button updateButton = (Button)include1.findViewById(R.id.updateButton);
		updateButton.setOnClickListener(new View.OnClickListener() {
			@Override
			public void onClick(View v) {

				EditText et;

				et = (EditText) include1.findViewById(R.id.editName);
				json.setName(et.getText().toString());

				et = (EditText) include1.findViewById(R.id.editFuri);
				json.setFuri(et.getText().toString());

				et = (EditText) include1.findViewById(R.id.editKyuyo);
				json.setKyuyo(Integer.parseInt(et.getText().toString()));

				Spinner spinner;

				spinner = (Spinner) include1.findViewById(R.id.spinnerSex);
				json.setSex(String.format("%d",spinner.getSelectedItemPosition()));

				spinner = (Spinner) include1.findViewById(R.id.spinnerSyozoku);
				json.setSyozokuFromSpinner(spinner);

				DatePicker dp = (DatePicker) include1.findViewById(R.id.datePickerBirthday);
				String stringDate = String.format("%d/%02d/%02d",
					dp.getYear(),dp.getMonth()+1,dp.getDayOfMonth()
					);
				json.setBirthday(stringDate);

				Tools.messageBox(mainActivity, "確認", "更新しますか?", new Tools.OnMessageBoxListener() {
					@Override
					public void onMessageBoxYesListener() {
						// 更新
						mDatabase
							.child("class")
							.child(json.getCode())
							.setValue(json)
							.addOnCompleteListener(new OnCompleteListener<Void>() {
								@Override
								public void onComplete(@NonNull Task<Void> task) {
									if (task.isSuccessful()) {
										Log.i("lightbox", "書き込みに成功しました");

										mainActivity.loadData();
										// 最初の画面へ移動
										vs.setDisplayedChild(MainActivity.FIRST_PAGE);
									}
									else {
										Log.i("lightbox", task.getException().toString());
										task.getException().printStackTrace();
										Toast.makeText(mainActivity,"更新に失敗しました",Toast.LENGTH_SHORT).show();
									}
								}
							});
					}

					@Override
					public void onMessageBoxNoListener() {

					}
				});


			}

		});
	}
}


JsonData クラス

データの表示時に Data Binding で使用され、データの取得と更新時には Firebase で使用される、とても重要なクラスです。

特に、テキスト以外の入力値が必要な画面コントロールに対して、Data Binding の @BindingAdapter を使用したカスタムセッターを、数値データ(画面へは文字列なので)、DatePicker、Spinner に合わせて作成してあります。
public class JsonData extends BaseObservable {

	private String code;
	private String name;
	private String furi;

	// 画面用のカスタムセッターと保存用の カスタムセッター が必要です
	private String birthday;
	private long kyuyo;
	private String sex;		// コンボボックス
	private String syozoku;		// コンボボックス

	// 未使用( 無いと Firebase のデータが消失するので )
	public String teate;
	public String kanri;

	// **************************************
	// Firebase で必要な空のコンストラクタ
	// **************************************
	public JsonData(){}

	// キーなので setter はありません
	public String getCode() {
		return code;
	}

	// **************************************
	// setter 内で通知するのに、@Bindable を getter に
	// **************************************
	@Bindable
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
		notifyPropertyChanged(lightbox.july.listviewandnextactivity.BR.name);
	}

	// **************************************
	// フリガナ
	// **************************************
	@Bindable
	public String getFuri() {
		return furi;
	}
	public void setFuri(String furi) {
		this.furi = furi;
		notifyPropertyChanged(lightbox.july.listviewandnextactivity.BR.furi);
	}

	// **************************************
	// 生年月日
	// **************************************
	@Bindable
	public String getBirthday() {
		return birthday;
	}
	public void setBirthday(String birthday) {
		this.birthday = birthday;
		notifyPropertyChanged(lightbox.july.listviewandnextactivity.BR.birthday);
	}
	@BindingAdapter("dateText")
	public static void setDateText(DatePicker datePicker, String text) {

		if ( text != null && !text.equals("")) {
			int year = Integer.parseInt(text.substring(0, 4));
			int month = Integer.parseInt(text.substring(5, 7)) - 1;
			int day = Integer.parseInt(text.substring(8, 10));
			datePicker.updateDate(year, month, day);
		}

	}
	// **************************************
	// 給与
	// **************************************
	@Bindable
	public long getKyuyo() {
		return kyuyo;
	}
	public void setKyuyo(long kyuyo) {
		this.kyuyo = kyuyo;
		notifyPropertyChanged(lightbox.july.listviewandnextactivity.BR.kyuyo);
	}
	@BindingAdapter("longKyuyo")
	public static void setLongKyuyo(EditText editKyuyo, long kyuyo) {
		editKyuyo.setText(String.format("%d",kyuyo));
	}

	// **************************************
	// 性別
	// **************************************
	@Bindable
	public String getSex() {
		return sex;
	}
	public void setSex(String sex) {
		this.sex = sex;
		notifyPropertyChanged(lightbox.july.listviewandnextactivity.BR.sex);
	}
	@BindingAdapter("stringSex")
	public static void setStringSex(Spinner spinnerSex, String sex) {
		if ( sex != null && !sex.equals("")) {
			spinnerSex.setSelection(Integer.parseInt(sex));
		}
	}

	// **************************************
	// 所属
	// **************************************
	@Bindable
	public String getSyozoku() {
		return syozoku;
	}
	public void setSyozoku(String syozoku) {
		this.syozoku = syozoku;
		notifyPropertyChanged(lightbox.july.listviewandnextactivity.BR.syozoku);
	}
	// コントロールから、syozoku をセットする  Setter
	// ※ setSyozoku という名称は使えません
	public void setSyozokuFromSpinner(Spinner spinnerSyozoku) {
		int position = spinnerSyozoku.getSelectedItemPosition();
		ArrayAdapter adapter = (ArrayAdapter) spinnerSyozoku.getAdapter();
		KeyValue kv = (KeyValue)adapter.getItem(position);
		this.setSyozoku(kv.getCode());
	}
	@BindingAdapter("syozokuData")
	public static void setSyozokuData(Spinner spinnerSyozoku, String syozoku) {
		if ( syozoku != null && !syozoku.equals("")) {
			ArrayAdapter adapter = (ArrayAdapter) spinnerSyozoku.getAdapter();
			int count = adapter.getCount();
			for( int i = 0; i < count; i++ ) {
				KeyValue kv = (KeyValue) adapter.getItem(i);
				if ( kv.getCode().equals(syozoku)) {
					spinnerSyozoku.setSelection(i);
					break;
				}
			}
		}
	}

	@Override
	public String toString() {
		return this.name;
	}
}

activity_next.xml

第二画面の定義です。Data Binding 用の記述として、app 名前空間で、カスタムセッターの名称を定義しています。
<layout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto">

    <data>
        <variable name="jdata" type="lightbox.july.listviewandnextactivity.JsonData"/>
    </data>

    <LinearLayout
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <LinearLayout
            android:orientation="horizontal"
            android:layout_width="match_parent"
            android:layout_height="match_parent">

            <Button
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="@string/btn_back"
                android:id="@+id/backButton"
                android:layout_alignParentTop="true"
                android:layout_alignParentStart="true"
                android:layout_weight="1"/>

            <Button
                android:text="@string/btn_update"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:id="@+id/updateButton"
                android:layout_weight="1"/>
        </LinearLayout>

        <ScrollView
            android:layout_width="match_parent"
            android:layout_height="match_parent">

            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:orientation="vertical">

                <TextView
                    android:text="@{jdata.code}"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:textAppearance="?android:attr/textAppearanceLarge"
                    android:id="@+id/textCode"/>

                <EditText
                    android:text="@{jdata.name}"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:inputType="textPersonName"
                    android:ems="10"
                    android:id="@+id/editName"/>

                <EditText
                    android:text="@{jdata.furi}"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:inputType="textPersonName"
                    android:ems="10"
                    android:id="@+id/editFuri"/>

                <EditText
                    app:longKyuyo="@{jdata.kyuyo}"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:inputType="number"
                    android:ems="10"
                    android:id="@+id/editKyuyo"/>

                <LinearLayout
                    android:orientation="horizontal"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:padding="10dp"
                    android:layout_marginLeft="5dp">

                    <Spinner
                        app:stringSex="@{jdata.sex}"
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"
                        android:id="@+id/spinnerSex"
                        android:layout_weight="1"
                        android:layout_marginRight="5dp"/>

                    <Spinner
                        app:syozokuData="@{jdata.syozoku}"
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"
                        android:id="@+id/spinnerSyozoku"
                        android:layout_weight="1"/>

                </LinearLayout>

                <DatePicker
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:datePickerMode="spinner"
                    android:calendarViewShown="false"
                    android:id="@+id/datePickerBirthday"
                    app:dateText="@{jdata.birthday}"/>

            </LinearLayout>

        </ScrollView>

    </LinearLayout>
</layout>

ListView 用の画面定義( Data Binding 仕様 )
<layout xmlns:android="http://schemas.android.com/apk/res/android">
    <data>
        <variable name="jdata" type="lightbox.july.listviewandnextactivity.JsonData"/>
    </data>
    <LinearLayout
                  android:orientation="vertical"
                  android:layout_width="match_parent"
                  android:layout_height="match_parent"
                  android:padding="10dp">

        <TextView
            android:text="@{jdata.name}"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:textAppearance="?android:attr/textAppearanceLarge"
            android:id="@+id/textItem1" />

        <TextView
            android:text="@{jdata.furi}"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:id="@+id/textItem2"
            android:layout_marginTop="5dp"/>

    </LinearLayout>
</layout>



posted by lightbox at 2016-11-01 20:17 | Comment(0) | Android Studio 2016 | このブログの読者になる | 更新情報をチェックする

2016年10月25日


Firebase storage に画像をアップロードする。1) ギャラリーから、2) 実行中の画面 / Android

Firebase の環境とプロジェクトの作成方法

9.6.1 を使用する場合
エミュレータ : SDK Manager で最新に更新
実機 : Google Play 開発者サービスを最新に更新

Firebase ドキュメント : Android でファイルをアップロードする
実行中の画面は、putBytes で、ギャラリーは putFile でアップロード可能です(putStream もあります)。Firebase storage のルールはテスト時のみ、allow write: if true; に変更して行いました。 Firebase リファレンス : StorageReference
public class MainActivity extends AppCompatActivity {

	private static final int READ_REQUEST_CODE = 42;

	private FirebaseStorage storage;
	private StorageReference storageRef;
	private StorageReference imageRef;
	private ProgressDialog progress;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);

// ▼ テスト用のルール
//		service firebase.storage {
//			match /b/freebase-654b7.appspot.com/o {
//				match /{allPaths=**} {
//					allow read;
//					allow write: if true;
//				}
//			}
//		}

		// アップロード中のダイアログ
		progress = new ProgressDialog(MainActivity.this);

		storage = FirebaseStorage.getInstance();
		storageRef = storage.getReferenceFromUrl("gs://freebase-654b7.appspot.com/");

		// 現在の画面をアップロード( View )
		Button captureButton = (Button) MainActivity.this.findViewById(R.id.captureButton);
		captureButton.setOnClickListener(new View.OnClickListener() {
			@Override
			public void onClick(View v) {

				// View から Bitmap 取得
				View view = v.getRootView();	// 画面全体
				view.setDrawingCacheEnabled(true);
				Bitmap cache = view.getDrawingCache();
				Bitmap rootViewCapture = Bitmap.createBitmap(cache);
				view.setDrawingCacheEnabled(false);

				// 画像アップロード用パス決定
				Calendar cal = Calendar.getInstance();
				SimpleDateFormat sf = new SimpleDateFormat("yyyyMMdd_HHmmss");
				String uploadImagePath = String.format("image/%s.png",sf.format(cal.getTime()));
				imageRef = storageRef.child(uploadImagePath);

				// byte[] に変換
				ByteArrayOutputStream baos = new ByteArrayOutputStream();
				rootViewCapture.compress(Bitmap.CompressFormat.PNG, 100, baos);
				byte[] data = baos.toByteArray();

				// アップロード中の表示
				progress.setProgressStyle(ProgressDialog.STYLE_SPINNER);
				progress.setMessage("画像をアップロードしています");
				progress.show();

				UploadTask uploadTask = imageRef.putBytes(data);
				uploadTask.addOnSuccessListener(new OnSuccessListener<UploadTask.TaskSnapshot>() {
					@Override
					public void onSuccess(UploadTask.TaskSnapshot taskSnapshot) {
						progress.dismiss();

						Log.i("lightbox","アップロードに成功しました");
						long size = taskSnapshot.getMetadata().getSizeBytes();
						Log.i("lightbox",String.format("サイズ : %d",size));

					}
				}).addOnFailureListener(new OnFailureListener() {
					@Override
					public void onFailure(@NonNull Exception e) {
						progress.dismiss();

						Log.i("lightbox","アップロードに失敗しました");
					}
				});
			}
		});

		// ギャラリーの画像をアップロード
		Button galleryButton = (Button) MainActivity.this.findViewById(R.id.galleryButton);
		galleryButton.setOnClickListener(new View.OnClickListener() {
			@Override
			public void onClick(View v) {
				Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
				intent.addCategory(Intent.CATEGORY_OPENABLE);
				intent.setType("image/*");
				startActivityForResult(intent, READ_REQUEST_CODE);
			}
		});

	}

	@Override
	protected void onActivityResult(int requestCode, int resultCode, Intent data) {
		super.onActivityResult(requestCode, resultCode, data);

		// ギャラリーからの戻り
		if (requestCode == READ_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
			Uri uri = null;
			if (data != null) {
				// Uri が戻されます
				uri = data.getData();
				Log.i("lightbox", "Uri: " + uri.toString());

				// 画像アップロード用パス決定
				ContentResolver contentResolver = MainActivity.this.getContentResolver();
				Cursor cursor = contentResolver.query(uri, null, null, null, null);
				cursor.moveToFirst();
				// とりあえず固定( 2 => _display_name )
				String name = cursor.getString(2);
				Log.i("lightbox", "FileName: " + name);

				//この カーソルの詳細
				String[] columnName = cursor.getColumnNames();
				for(int i = 0; i < columnName.length; i++) {
					Log.i("lightbox", String.format("%d : %s : %s",i, columnName[i], cursor.getString(i)));
				}

				String uploadImagePath = String.format("image/%s",name);
				imageRef = storageRef.child(uploadImagePath);

				// アップロード中の表示
				progress.setProgressStyle(ProgressDialog.STYLE_SPINNER);
				progress.setMessage("画像をアップロードしています");
				progress.show();

				// 画像アップロード
				UploadTask uploadTask = imageRef.putFile(uri);
				uploadTask.addOnSuccessListener(new OnSuccessListener<UploadTask.TaskSnapshot>() {
					@Override
					public void onSuccess(UploadTask.TaskSnapshot taskSnapshot) {
						progress.dismiss();

						Log.i("lightbox","アップロードに成功しました");
						long size = taskSnapshot.getMetadata().getSizeBytes();
						Log.i("lightbox",String.format("サイズ : %d",size));

					}
				}).addOnFailureListener(new OnFailureListener() {
					@Override
					public void onFailure(@NonNull Exception e) {
						progress.dismiss();

						Log.i("lightbox","アップロードに失敗しました");
					}
				});

			}

		}
	}

}



build.gradle(Project)
buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:2.2.2'
        classpath 'com.google.gms:google-services:3.0.0'
    }
}

allprojects {
    repositories {
        jcenter()
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}


build.gradle(Module: app)
apply plugin: 'com.android.application'

android {
    compileSdkVersion 22
    buildToolsVersion "23.0.3"

    defaultConfig {
        applicationId "lightbox.sep.fire1"
        minSdkVersion 19
        targetSdkVersion 22
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    testCompile 'junit:junit:4.12'
    compile 'com.android.support:appcompat-v7:22.2.1'
    compile 'com.google.firebase:firebase-core:9.6.1'
    compile 'com.google.firebase:firebase-storage:9.6.1'
    compile 'com.google.firebase:firebase-auth:9.6.1'
}

apply plugin: 'com.google.gms.google-services'
※ AndroidManifest.xml には、android.permission.INTERNET を設定


posted by lightbox at 2016-10-25 12:21 | Comment(0) | Android Studio 2016 | このブログの読者になる | 更新情報をチェックする

Android : 画像関連のテスト用カメラアプリ

カメラそのものをどうこうしたいのでは無く、他の機能(Firebase storage 等)との連携テストに使いたいので、いわゆる旧式のカメラ API を使用しています。

▼ Android Studio では 『非推奨』と言われます


また、撮影は AndroidManifest.xml で縦の portrait 固定にして行う為、実際に横にした時の判定にセンサを使い、縦の時は 90度回転して画像を保存しなおしています。テストの実機は 4.4.4 のみですが、そもそもテスト用なので全ての対応はソース次第で好きに変更できます。

いろいろサンプルを探したのですが、全方向に対応したものが無かったので機能を集めました。

※ センサの判定はかなり適当です

MyCamera クラス
public class MyCamera implements SurfaceHolder.Callback {

	// 古いカメラAPI。カメラで何かしたいわけでは無いのでこれで実装
	private Camera camera;
	private SurfaceHolder holder;
	private Camera.Size size;
	private MainActivity mainActivity;
	private SurfaceView cameraView;
	private List<Camera.Size> sizeList;

	public MyCamera(Context context) {

		Log.i("lightbox", "コンストラクタ");

		this.mainActivity = (MainActivity) context;

		// MainActivity の画面のSurfaceView を使う
		cameraView = (SurfaceView)mainActivity.findViewById(R.id.cameraView);
		// SurfaceView から holder を取り出して、カメラに渡す
		holder = cameraView.getHolder();
		// この中でイベント処理を行う
		holder.addCallback(MyCamera.this);

	}

	public Camera getCamera() {

		return camera;

	}

	@Override
	public void surfaceCreated(SurfaceHolder holder) {

		Log.i("lightbox", "開く");

		// カメラを開く
		camera = Camera.open();
		try {
			// カメラに holder を渡す
			camera.setPreviewDisplay(holder);
		} catch (IOException e) {
			e.printStackTrace();
		}

		// 縦固定(AndroidManifest.xml) で、portrait 撮影を前提
		camera.setDisplayOrientation(90);

		// カメラの情報
		Camera.Parameters params = camera.getParameters();
		// サポートされているサイズの一覧
		sizeList = params.getSupportedPreviewSizes();
		// 対応しているサイズの先頭のサイズ
		size = sizeList.get(0);

	}

	@Override
	public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

		// 縦固定(AndroidManifest.xml) なので一度しか通らない

		Log.i("lightbox", "開始");

		// 縦横サイズの設定
		Camera.Parameters params = camera.getParameters();
		// ギャラリーでは縦になるが、保存画像は横
		params.setRotation(90);
		Camera.Size optimalSize = getOptimalPreviewSize(sizeList,width,height);
		params.setPreviewSize(optimalSize.width,optimalSize.height);
		camera.setParameters(params);

		camera.startPreview();

	}

	// http://qiita.com/zaburo/items/b5d3815d3ec45b0daf4f
	private Camera.Size getOptimalPreviewSize(List<Camera.Size> sizes, int w, int h) {

		final double ASPECT_TOLERANCE = 0.1;
		double targetRatio=(double)h / w;

		if (sizes == null) return null;

		Camera.Size optimalSize = null;
		double minDiff = Double.MAX_VALUE;

		int targetHeight = h;

		for (Camera.Size size : sizes) {
			double ratio = (double) size.width / size.height;
			if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE) continue;
			if (Math.abs(size.height - targetHeight) < minDiff) {
				optimalSize = size;
				minDiff = Math.abs(size.height - targetHeight);
			}
		}

		if (optimalSize == null) {
			minDiff = Double.MAX_VALUE;
			for (Camera.Size size : sizes) {
				if (Math.abs(size.height - targetHeight) < minDiff) {
					optimalSize = size;
					minDiff = Math.abs(size.height - targetHeight);
				}
			}
		}
		return optimalSize;
	}


	@Override
	public void surfaceDestroyed(SurfaceHolder holder) {

		Log.i("lightbox", "終了");

		// プレビュー終了
		if ( camera != null ) {
			camera.stopPreview();
			camera.release();
			camera = null;
		}

	}
}


MainActivity
public class MainActivity extends AppCompatActivity implements SensorEventListener {

	private String imagePath;
	private MyCamera camera;
	private SensorManager sensor;
	private Sensor acc;
	private boolean portrait;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);

		camera = new MyCamera(MainActivity.this);
		sensor = (SensorManager)getSystemService(SENSOR_SERVICE);
		acc = sensor.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
		sensor.registerListener(MainActivity.this,acc,SensorManager.SENSOR_DELAY_NORMAL);
		portrait = true;

		// *************************************
		// ギャラリーに保存
		// *************************************
		Button galleryButton = (Button) MainActivity.this.findViewById(R.id.galleryButton);
		galleryButton.setOnClickListener(new View.OnClickListener() {
			@Override
			public void onClick(View v) {
				Log.i("lightbox", "クリック");

				Camera.Parameters params = camera.getCamera().getParameters();
				if ( portrait ) {
					// ギャラリーでは縦になるが、保存画像は横
					params.setRotation(90);
				}
				else {
					// ギャラリーで横、保存画像も横
					params.setRotation(0);
				}
				camera.getCamera().setParameters(params);


				// 撮影
				camera.getCamera().takePicture(null, null, new Camera.PictureCallback() {
					@Override
					public void onPictureTaken(byte[] data, Camera camera) {

						if (data != null) {

							// ギャラリー用に内部ストレージにフォルダを作成
							String firebaseImageDir = Environment.getExternalStorageDirectory().getPath() + "/firebase";
							File file = new File(firebaseImageDir);
							// ディレクトリ初期作成
							if (!file.exists()) {
								if (file.mkdir() == false) {
									Log.i("lightbox", "ディレクトリを作成できませんでした");
									return;
								}
							}

							// ギャラリー用画像保存パス
							Calendar cal = Calendar.getInstance();
							SimpleDateFormat sf = new SimpleDateFormat("yyyyMMdd_HHmmss");
							imagePath = firebaseImageDir + "/" + sf.format(cal.getTime()) + ".jpg";

							FileOutputStream jpg;
							try {
								jpg = new FileOutputStream(imagePath);
								if ( portrait ) {
									// 縦の場合、回転して保存する
									Bitmap bmp1 = BitmapFactory.decodeByteArray (data, 0, data.length);
									int width = bmp1.getWidth();
									int height = bmp1.getHeight();
									Matrix matrix = new Matrix();
									matrix.postRotate (90);
									Bitmap bmp2 = Bitmap.createBitmap (bmp1, 0, 0, width, height, matrix, true);

									bmp2.compress(Bitmap.CompressFormat.JPEG,90,jpg);
								}
								else {
									jpg.write(data);
									jpg.close();
								}

								// ギャラリーに反映
								MediaScannerConnection.scanFile(
									MainActivity.this,
									new String[] { imagePath },
									new String[] { "image/jpeg" },
									null);

							} catch (Exception e) {
								e.printStackTrace();
							}

							// イベントの引数のカメラ
							camera.startPreview();

						}
					}
				});

			}
		});

	}

	@Override
	public void onAccuracyChanged(Sensor sensor, int accuracy) {

	}

	@Override
	public void onSensorChanged(SensorEvent event) {
		// センサで実際の縦と横を判定
		if ( -4 <= event.values[0] && event.values[0] <= 4  ) {
			portrait = true;
		}
		else {
			portrait = false;
		}
	}

	public boolean getPortrait (){
		return portrait;
	}

	@Override
	protected void onResume() {
		super.onResume();
		sensor.registerListener(MainActivity.this,acc,SensorManager.SENSOR_DELAY_NORMAL);

	}

	@Override
	protected void onPause() {
		super.onPause();
		sensor.unregisterListener(this);
	}


}


AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest package="lightbox.sep.fire1"
          xmlns:android="http://schemas.android.com/apk/res/android">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity
            android:name=".MainActivity"
            android:screenOrientation="portrait">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>

                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>

    </application>
    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.CAMERA"/>

</manifest>



posted by lightbox at 2016-10-25 03:15 | Comment(0) | Android Studio 2016 | このブログの読者になる | 更新情報をチェックする

2016年10月18日


Firebase storage の画像を ファイルとしてダウンロードして ギャラリーに保存する( ImageView にも表示する ) / Android

Firebase の環境とプロジェクトの作成方法

9.6.1 を使用する場合
エミュレータ : SDK Manager で最新に更新
実機 : Google Play 開発者サービスを最新に更新

Firebase ドキュメント : ローカル ファイルにダウンロードする
MainActivity galleryFile に直接ダウンロードすると簡単ですが、createTempFile によって、アプリのキャッシュにいったん保存して処理したほうが、いろんな情報と関連付けできるのでそうしています。キャッシュに作成したファイルは最後で削除はしていますが、アプリの設定画面の『キャッシュを消去』でクリアできました。 ※ ダウンロードする画像のタイプはここではチェックしていません
public class MainActivity extends AppCompatActivity {

	// ダウンロード用
	private FirebaseStorage storage;
	private StorageReference storageRef;
	private StorageReference imageRef;
	private ImageView imageView;
	private File galleryFile;
	private String imagePath;
	private File localFile;
	private ProgressDialog progress;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);

// ▼ テスト用のルール
//		service firebase.storage {
//			match /b/freebase-654b7.appspot.com/o {
//				match /{allPaths=**} {
//					allow read;
//					allow write: if true;
//				}
//			}
//		}

		// ダウンロードするファイル
		storage = FirebaseStorage.getInstance();
		storageRef = storage.getReferenceFromUrl("gs://freebase-654b7.appspot.com/");
		imageRef = storageRef.child("sworc2.png");

		// 表示する場所
		imageView = (ImageView) MainActivity.this.findViewById(R.id.imageView);

		// ダウンロード中のダイアログ
		progress = new ProgressDialog(MainActivity.this);

		// *************************************
		// ギャラリーにダウンロードして表示
		// *************************************
		Button galleryButton = (Button) MainActivity.this.findViewById(R.id.galleryButton);
		galleryButton.setOnClickListener(new View.OnClickListener() {
			@Override
			public void onClick(View v) {

				// ダウンロード中の表示
				progress.setProgressStyle(ProgressDialog.STYLE_SPINNER);
				progress.setMessage("画像をダウンロードしています");
				progress.show();

				// ギャラリー用に内部ストレージにフォルダを作成
				String firebaseImageDir = Environment.getExternalStorageDirectory().getPath() + "/firebase";
				File file = new File(firebaseImageDir);
				// ディレクトリ初期作成
				if (!file.exists()) {
					if (file.mkdir() == false) {
						Log.i("lightbox", "ディレクトリを作成できませんでした");
						return;
					}
				}

				// ギャラリー用画像保存パス
				Calendar cal = Calendar.getInstance();
				SimpleDateFormat sf = new SimpleDateFormat("yyyyMMdd_HHmmss");
				imagePath = firebaseImageDir + "/" + sf.format(cal.getTime()) + ".png";
				galleryFile = new File(imagePath);

				localFile = null;
				try {
					// 第三引数省略時、System.getProperty("java.io.tmpdir") に作成
					// ( このアプリの キャッシュ )
					// 例) /data/data/lightbox.sep.fire1/cache
					localFile = File.createTempFile("sworc2", "png");
				} catch (IOException e) {
					e.printStackTrace();
				}
				if ( localFile == null ) {
					Log.i("lightbox","テンポラリファイルを作成できませんでした");
					return;
				}

				Log.i("lightbox","実際の TempFile のパス");
				Log.i("lightbox",localFile.getAbsolutePath());

				// ファイルにダウンロード
				imageRef.getFile(localFile).addOnSuccessListener(new OnSuccessListener<FileDownloadTask.TaskSnapshot>() {
					@Override
					public void onSuccess(FileDownloadTask.TaskSnapshot taskSnapshot) {

						// ダウンロード中の表示解除
						progress.dismiss();

						// ダウンロードした localFile をファイルコピー
						try {
							FileInputStream inStream = new FileInputStream(localFile);
							FileOutputStream outStream = new FileOutputStream(galleryFile);
							FileChannel inChannel = inStream.getChannel();
							FileChannel outChannel = outStream.getChannel();
							inChannel.transferTo(0, inChannel.size(), outChannel);
							inStream.close();
							outStream.close();
						} catch (IOException e) {
							e.printStackTrace();
						}

						Log.i("lightbox","キャッシュ内の一覧");
						File[] files = new File(System.getProperty("java.io.tmpdir")).listFiles();
						for(int i = 0; i < files.length; i++){
							Log.i("lightbox",files[i].getAbsolutePath());
						}

						// ギャラリーに反映
						MediaScannerConnection.scanFile(
							MainActivity.this,
							new String[] { imagePath },
							new String[] { "image/png" },
							null);

						// ImageView に表示
						FileInputStream fis = null;
						try {
							fis = new FileInputStream(localFile);
							if ( fis != null ) {
								Bitmap image = BitmapFactory.decodeStream(fis);
								imageView.setImageBitmap(image);
								// キャッシュファイルの削除
								localFile.delete();
							}
						} catch (FileNotFoundException e) {
							e.printStackTrace();
						}

					}
				}).addOnFailureListener(new OnFailureListener() {
					@Override
					public void onFailure(@NonNull Exception e) {
						Log.i("lightbox","ダウンロードできませんでした");
						Log.i("lightbox",e.getMessage());
						// ダウンロード中の表示解除
						progress.dismiss();
						// メッセージ表示
						Toast.makeText(MainActivity.this,"ダウンロードに失敗しました",Toast.LENGTH_LONG).show();

					}
				});

			}
		});

	}
}

テンポラリファイルを使わずにダイレクトに保存するソースコード

build.gradle(Project)
buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:2.2.1'
        classpath 'com.google.gms:google-services:3.0.0'
    }
}

allprojects {
    repositories {
        jcenter()
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}


build.gradle(Module: app)
apply plugin: 'com.android.application'

android {
    compileSdkVersion 22
    buildToolsVersion "23.0.3"

    defaultConfig {
        applicationId "lightbox.sep.fire1"
        minSdkVersion 19
        targetSdkVersion 22
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    testCompile 'junit:junit:4.12'
    compile 'com.android.support:appcompat-v7:22.2.1'
    compile 'com.google.firebase:firebase-core:9.6.1'
    compile 'com.google.firebase:firebase-storage:9.6.1'
    compile 'com.google.firebase:firebase-auth:9.6.1'
}

apply plugin: 'com.google.gms.google-services'
※ AndroidManifest.xml には、android.permission.INTERNET を設定
※ AndroidManifest.xml には、android.permission.WRITE_EXTERNAL_STORAGE を設定

posted by lightbox at 2016-10-18 21:41 | Comment(0) | Android Studio 2016 | このブログの読者になる | 更新情報をチェックする

Firebase storage の画像の URL を取得して、通常と同様に Stream でダウンロードして ImageView に表示する / Android

Firebase の環境とプロジェクトの作成方法

9.6.1 を使用する場合
エミュレータ : SDK Manager で最新に更新
実機 : Google Play 開発者サービスを最新に更新

Firebase ドキュメント : URL 経由でデータをダウンロードする
getDownloadUrl メソッドで Uri を取得した後は、通常どおり AsyncTask 内で Stream で読み込みます。 MainActivity
public class MainActivity extends AppCompatActivity {

	// ダウンロード用
	private FirebaseStorage storage;
	private StorageReference storageRef;
	private StorageReference imageRef;
	private ImageView imageView;
	private ProgressDialog progress;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);

// ▼ テスト用のルール
//		service firebase.storage {
//			match /b/freebase-654b7.appspot.com/o {
//				match /{allPaths=**} {
//					allow read;
//					allow write: if true;
//				}
//			}
//		}

		// ダウンロードするファイル
		storage = FirebaseStorage.getInstance();
		storageRef = storage.getReferenceFromUrl("gs://freebase-654b7.appspot.com/");
		imageRef = storageRef.child("sworc2.png");

		// 表示する場所
		imageView = (ImageView) MainActivity.this.findViewById(R.id.imageView);

		// ダウンロード中のダイアログ
		progress = new ProgressDialog(MainActivity.this);

		// *************************************
		// 表示2 ( Uri を取得 => Stream )
		// *************************************
		Button button = (Button) MainActivity.this.findViewById(R.id.downloadButton);
		button.setOnClickListener(new View.OnClickListener() {
			@Override
			public void onClick(View v) {

				// ダウンロード中の表示
				progress.setProgressStyle(ProgressDialog.STYLE_SPINNER);
				progress.setMessage("画像をダウンロードしています");
				progress.show();

				// ダウンロード用の URL を取得
				imageRef.getDownloadUrl()
					.addOnSuccessListener(new OnSuccessListener<Uri>() {
						@Override
						public void onSuccess(Uri uri) {
							Log.i("lightbox","ダウンロード開始");

							new AsyncTask<Uri, Void, Bitmap>(){
								@Override
								protected Bitmap doInBackground(Uri... params) {

									Uri uri = params[0];
									Log.i("lightbox",uri.toString());
									URL url = null;
									try {
										url = new URL(uri.toString());
									} catch (Exception e) {
										e.printStackTrace();
									}
									Bitmap image = null;
									// 読み込みオプション
									BitmapFactory.Options options;
									try {
										// インターネット上の画像を取得して、Bitmap に変換
										options = new BitmapFactory.Options();
										// 実際に読み込む事をオプションに設定
										options.inJustDecodeBounds = false;
										// ストリーム取得
										InputStream is = (InputStream) url.getContent();
										// ストリームよりビットマップを作成
										image = BitmapFactory.decodeStream(is, null, options);
										// ストリームを閉じる
										is.close();
									} catch (Exception e) {
										e.printStackTrace();
									}
									return image;
								}

								@Override
								protected void onPostExecute(Bitmap bitmap) {
									if ( bitmap != null ) {
										imageView.setImageBitmap(bitmap);
									}
									// ダウンロード中の表示解除
									progress.dismiss();

								}
							}.execute(uri);

						}
					})
					.addOnFailureListener(new OnFailureListener() {
						@Override
						public void onFailure(@NonNull Exception e) {
							Log.i("lightbox","データ取得に失敗しました");
							Log.i("lightbox",e.getMessage());
							// ダウンロード中の表示解除
							progress.dismiss();
							// メッセージ表示
							Toast.makeText(MainActivity.this,"ダウンロードに失敗しました",Toast.LENGTH_LONG).show();
						}
					});

			}
		});

	}
}


build.gradle(Project)
buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:2.2.1'
        classpath 'com.google.gms:google-services:3.0.0'
    }
}

allprojects {
    repositories {
        jcenter()
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}


build.gradle(Module: app)
apply plugin: 'com.android.application'

android {
    compileSdkVersion 22
    buildToolsVersion "23.0.3"

    defaultConfig {
        applicationId "lightbox.sep.fire1"
        minSdkVersion 19
        targetSdkVersion 22
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    testCompile 'junit:junit:4.12'
    compile 'com.android.support:appcompat-v7:22.2.1'
    compile 'com.google.firebase:firebase-core:9.6.1'
    compile 'com.google.firebase:firebase-storage:9.6.1'
    compile 'com.google.firebase:firebase-auth:9.6.1'
}

apply plugin: 'com.google.gms.google-services'
※ auth はここでは使用していません
※ AndroidManifest.xml には、android.permission.INTERNET を設定


posted by lightbox at 2016-10-18 18:46 | Comment(0) | Android Studio 2016 | このブログの読者になる | 更新情報をチェックする

Firebase storage の画像をメモリに直接ダウンロードして ImageView に表示する / Android

Firebase の環境とプロジェクトの作成方法

9.6.1 を使用する場合
エミュレータ : SDK Manager で最新に更新
実機 : Google Play 開発者サービスを最新に更新

Firebase ドキュメント : メモリにダウンロードする
メモリへのダウンロードは、画像の大きさを考慮しておれば、他は特別な内容はありません。ストレージのルールは allow read; にしておいて、ログインなしでテストしています。 ※ 取得した Byte の配列から Bitmap の作成は BitmapFactory.decodeByteArray で行っています。 MainActivity
public class MainActivity extends AppCompatActivity {

	static long MAX_SIZE_BYTE = 1024 * 100;
	// ダウンロード用
	private FirebaseStorage storage;
	private StorageReference storageRef;
	private StorageReference imageRef;
	private ImageView imageView;
	private ProgressDialog progress;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);

// ▼ テスト用のルール
//		service firebase.storage {
//			match /b/freebase-654b7.appspot.com/o {
//				match /{allPaths=**} {
//					allow read;
//					allow write: if true;
//				}
//			}
//		}

		// ダウンロードするファイル
		storage = FirebaseStorage.getInstance();
		storageRef = storage.getReferenceFromUrl("gs://freebase-654b7.appspot.com/");
		imageRef = storageRef.child("sworc2.png");

		// 表示する場所
		imageView = (ImageView) MainActivity.this.findViewById(R.id.imageView);

		// ダウンロード中のダイアログ
		progress = new ProgressDialog(MainActivity.this);

		// *************************************
		// 表示1 ( メモリに取得 : bytes[] )
		// *************************************
		Button byteButton = (Button) MainActivity.this.findViewById(R.id.byteButton);
		byteButton.setOnClickListener(new View.OnClickListener() {
			@Override
			public void onClick(View v) {
				// ダウンロード中の表示
				progress.setProgressStyle(ProgressDialog.STYLE_SPINNER);
				progress.setMessage("画像をダウンロードしています");
				progress.show();

				// ダウンロード
				imageRef.getBytes(MainActivity.MAX_SIZE_BYTE)
					.addOnSuccessListener(new OnSuccessListener<byte[]>() {
					@Override
					public void onSuccess(byte[] bytes) {
						Log.i("lightbox","データ取得に成功しました");
						Log.i("lightbox", String.format("%d",bytes.length));
						if (bytes != null) {
							Bitmap image = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
							imageView.setImageBitmap(image);
						}
						// ダウンロード中の表示解除
						progress.dismiss();
					}
				}).addOnFailureListener(new OnFailureListener() {
					@Override
					public void onFailure(@NonNull Exception e) {
						// *****************************************
						// ルールを allow read: if false; にすると失敗します
						// => User does not have permission to access this object.
						// ファイルが存在しない場合
						// => Object does not exist at location.
						// *****************************************
						Log.i("lightbox","データ取得に失敗しました");
						Log.i("lightbox",e.getMessage());
						e.printStackTrace();
						// ダウンロード中の表示解除
						progress.dismiss();
						// メッセージ表示
						Toast.makeText(MainActivity.this,"ダウンロードに失敗しました",Toast.LENGTH_LONG).show();
					}
				});

			}
		});

	}
}


build.gradle(Project)
buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:2.2.1'
        classpath 'com.google.gms:google-services:3.0.0'
    }
}

allprojects {
    repositories {
        jcenter()
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}


build.gradle(Module: app)
apply plugin: 'com.android.application'

android {
    compileSdkVersion 22
    buildToolsVersion "23.0.3"

    defaultConfig {
        applicationId "lightbox.sep.fire1"
        minSdkVersion 19
        targetSdkVersion 22
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    testCompile 'junit:junit:4.12'
    compile 'com.android.support:appcompat-v7:22.2.1'
    compile 'com.google.firebase:firebase-core:9.6.1'
    compile 'com.google.firebase:firebase-storage:9.6.1'
    compile 'com.google.firebase:firebase-auth:9.6.1'
}

apply plugin: 'com.google.gms.google-services'
※ AndroidManifest.xml には、android.permission.INTERNET を設定


posted by lightbox at 2016-10-18 14:35 | Comment(0) | Android Studio 2016 | このブログの読者になる | 更新情報をチェックする

2016年10月17日


Android : Firebase の データを REST API の PUT コマンドで更新する

Firebase の環境とプロジェクトの作成方法

▼ 表示 / 更新用の Firebase URL
https://freebase-654b7.firebaseio.com/class.json
▼ 構成 インターネットデータの読み込みと メッセージボックスの処理は、tools.jar を使用しています。 Firebase における REST API どうも、単純読み出しと更新しか現時点(2016/10/17)では、あてにならないようです。少なくとも、以前のバージョンではソートができていたはずなのですが、現在正しく動きません。というか、そもそも現在の最新ドキュメントに何も書いてくれていません。なので、User Authentication もダメです。しばらくは放置しておいたほうが賢明だと思います。 JSON 文字列を引き渡す更新に関しては、PHP からも簡単に実装できます。Android からも特別な事は必要ありません。 Android : Firebase 用 HttpPut クラス ( URL を非公開にして、ルールを ".write" : true で使用するといいです ) Firebase API を使用すれば、REST API を使用せずとも、ソートや更新が簡単にできますが、他の API との互換性等を考えた場合、このような方法(REST API)も必要かと思います。 ListView と ViewSwitcher アクティビティは、取り回ししやすいように MainActivity のみです。ListView からの1件の表示は ViewSwitcher に設定した別画面(include)で行い、処理は専用の処理クラス(NextPage) を作成してそこで記述するようにしています。ここでは、new GsonBuilder().setPrettyPrinting().create() を使って、JSON 文字列を整形していますが、処理には Gson gson = new Gson() で十分です。 MainActivity REST API では、ソートができないので、ArrayList をソートして使用します。JSON 文字列の扱いは、Google Gson で行います。
public class MainActivity extends AppCompatActivity {

	public static int FIRST_PAGE = 0;
	public static int NEXT_PAGE = 1;
	private MyArrayAdapter adapter;
	private ListView listview;
	private ViewSwitcher vs;
	private NextPage nextPage;
	private Gson gson;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);

		// 画面表示
		MainActivity.this.setContentView(R.layout.activity_main);

		// 複数画面処理
		vs = (ViewSwitcher) MainActivity.this.findViewById(R.id.viewSwitcher);
		// 次画面処理
		nextPage = new NextPage(MainActivity.this);
		// イベント登録
		nextPage.initAction();

		// アダプタを作成
		adapter = new MyArrayAdapter(MainActivity.this,R.layout.list_item);

		// リストビューの取得
		listview = (ListView) MainActivity.this.findViewById(R.id.listView);
		// リストビューの行をタップした時の処理
		listview.setOnItemClickListener(new AdapterView.OnItemClickListener() {
			@Override
			public void onItemClick(AdapterView<?> parent, View view, int position, long id) {

				// アダプターを取得
				MyArrayAdapter adapter = (MyArrayAdapter)parent.getAdapter();
				// 行データを取得
				JsonData json = (JsonData)adapter.getItem(position);

				String jsonString = gson.toJson(json,JsonData.class);
				Log.i("lightbox",jsonString);

				View include1 = MainActivity.this.findViewById(R.id.include1);

				TextView tv = (TextView) include1.findViewById(R.id.textKey);
				tv.setText(json.getCode());

				EditText et;
				et =  (EditText) include1.findViewById(R.id.editName);
				et.setText(json.getName());
				et =  (EditText) include1.findViewById(R.id.editFuri);
				et.setText(json.getFuri());

				nextPage.setJson(json);

				// 画面移動
				vs.setDisplayedChild(MainActivity.NEXT_PAGE);
			}
		});
		// リストビューにデータを表示
		listview.setAdapter(adapter);

		// JSON データの URL ( Firebase を使用 )
		String json_url = "https://freebase-654b7.firebaseio.com/class.json?print=pretty";
		// tools.jar の static メソッド
		Tools.callHttpGet(json_url, "utf-8", new Tools.OnAsyncTaskListener() {
			@Override
			public void onAsyncTaskListener(String s) {

				// JSON データをデシリアライズ
				gson = new GsonBuilder().setPrettyPrinting().create();
				Type myMap = new TypeToken<Map<String, JsonData>>(){}.getType();
				Map<String,JsonData> map = gson.fromJson(s, myMap);

				adapter.clear();
				// map.values() から ArrayList を作成
				ArrayList al = new ArrayList<JsonData>(map.values());
				// ソート
				Collections.sort(al, new Comparator<JsonData>() {
					public int compare(JsonData a, JsonData b) {
						String  name_a = a.getFuri();
						String  name_b = b.getFuri();

						return name_a.compareTo(name_b);
					}

				});
				// アダプターにセット
				adapter.addAll(al);

			}
		});

	}

}



NextPage
public class NextPage {

	private MainActivity mainActivity;
	private ViewSwitcher vs;
	private JsonData json;

	public NextPage(MainActivity mainActivity) {
		this.mainActivity = mainActivity;
	}

	public void setJson(JsonData json) {
		this.json = json;
	}

	public void initAction(){

		// 複数画面処理
		vs = (ViewSwitcher) mainActivity.findViewById(R.id.viewSwitcher);

		// activity_next.xml の 親 view です
		View include1 = mainActivity.findViewById(R.id.include1);

		// *****************************************
		// 戻るボタン
		// *****************************************
		Button backButton = (Button)include1.findViewById(R.id.backButton);
		backButton.setOnClickListener(new View.OnClickListener() {
			@Override
			public void onClick(View v) {

				// 最初の画面へ移動
				vs.setDisplayedChild(MainActivity.FIRST_PAGE);

			}

		});

		// *****************************************
		// 更新ボタン
		// *****************************************
		Button updateButton = (Button)include1.findViewById(R.id.updateButton);
		updateButton.setOnClickListener(new View.OnClickListener() {
			@Override
			public void onClick(View v) {

				Tools.messageBox(mainActivity, "更新確認", "更新しますか?", new Tools.OnMessageBoxListener() {
					@Override
					public void onMessageBoxYesListener() {
						updateData();
					}

					@Override
					public void onMessageBoxNoListener() {

					}
				});

			}

		});


	}

	private void updateData(){
		View include1 = mainActivity.findViewById(R.id.include1);

		EditText et;
		et =  (EditText) include1.findViewById(R.id.editName);
		json.setName(et.getText().toString());
		et =  (EditText) include1.findViewById(R.id.editFuri);
		json.setFuri(et.getText().toString());

		Gson gson = new GsonBuilder().setPrettyPrinting().create();
		String jsonString = gson.toJson(json,JsonData.class);
		String url = String.format("https://freebase-654b7.firebaseio.com/class/%s.json", json.getCode());

		new AsyncTask<String,Void,String>(){
			@Override
			protected String doInBackground(String... params) {
				String result = HttpPut.execute(params[0],params[1],"utf-8");
				Log.i("lightbox", result);
				return result;
			}

			@Override
			protected void onPostExecute(String s) {
				// 最初の画面へ移動
				vs.setDisplayedChild(MainActivity.FIRST_PAGE);
			}
		}.execute(url,jsonString);

	}
}


activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="16dp"
    tools:context=".MainActivity">

    <ViewSwitcher
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/viewSwitcher">

        <ListView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:id="@+id/listView"/>

        <include
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            layout="@layout/activity_next"
            android:id="@+id/include1"/>

    </ViewSwitcher>

</LinearLayout>


MyArrayAdapter

オーソドックスな、ListView 用の 専用 ArrayAdapter です。
public class MyArrayAdapter extends ArrayAdapter<JsonData> {

	// JsonData を格納する、この処理専用の
	// ArrayAdapter のカスタマイズ

	// コンストラクタで渡された画面の保存
	private int mResource;

	public MyArrayAdapter(Context context, int resource) {
		super(context, resource);
		// ArrayAdapter でも、このようにして保存して利用してます
		mResource = resource;
	}

	@Override
	public View getView(int position, View convertView, ViewGroup parent) {

		if (convertView == null) {
			// 現在の View の取得
			// getContext() で super で渡されたものを取得できます
			LayoutInflater inflater
				= (LayoutInflater) MyArrayAdapter.this.getContext().getSystemService
				(Context.LAYOUT_INFLATER_SERVICE);
			// super で渡されたものは取得できないので自前で用意します
			convertView = inflater.inflate(mResource, null);
		}

		// アダプターより行データを取得
		JsonData json = MyArrayAdapter.this.getItem(position);

		// 画面にデータをセット
		TextView tv;

		// キー
		tv = (TextView) convertView.findViewById(R.id.textKey);
		tv.setText(json.getCode());

		// Subject
		tv	= (TextView) convertView.findViewById(R.id.textItem1);
		tv.setText(json.getName());

		// Name
		tv = (TextView) convertView.findViewById(R.id.textItem2);
		tv.setText(json.getFuri());

		// 行の画面をシステムに返す
		return convertView;
	}

}


JsonData

Android Studio では、private の変数を作っておいて、ALT + Insert から Getter and Setter で一気に作成できます。toString() はここでは特に必要ありませんが、Android の単純リソースを使用するなら必要です。

空のコンストラクタも、新規用のコンストラクタもここでは使用しませんが、前者は Firebase API を使用する際には必要になります。後者も使用しませんが、ALT + Insert で簡単に作成できます。

※ kyuyo が int ではなく long なのも、Firebase API の都合です。
public class JsonData {

	private String code;
	private String name;
	private String furi;
	private String birthday;
	private String kanri;
	private long kyuyo;
	private String sex;
	private String syozoku;
	private String teate;

	public JsonData(){}

	public JsonData(String birthday, String code, String furi, String kanri, long kyuyo, String name, String sex, String syozoku, String teate) {
		this.birthday = birthday;
		this.code = code;
		this.furi = furi;
		this.kanri = kanri;
		this.kyuyo = kyuyo;
		this.name = name;
		this.sex = sex;
		this.syozoku = syozoku;
		this.teate = teate;
	}

	public String getCode() {
		return code;
	}
	public void setCode(String code) {
		this.code = code;
	}

	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}

	public String getFuri() {
		return furi;
	}
	public void setFuri(String furi) {
		this.furi = furi;
	}

	public String getBirthday() {
		return birthday;
	}
	public void setBirthday(String birthday) {
		this.birthday = birthday;
	}

	public String getKanri() {
		return kanri;
	}
	public void setKanri(String kanri) {
		this.kanri = kanri;
	}

	public long getKyuyo() {
		return kyuyo;
	}
	public void setKyuyo(long kyuyo) {
		this.kyuyo = kyuyo;
	}

	public String getSex() {
		return sex;
	}
	public void setSex(String sex) {
		this.sex = sex;
	}

	public String getSyozoku() {
		return syozoku;
	}
	public void setSyozoku(String syozoku) {
		this.syozoku = syozoku;
	}

	public String getTeate() {
		return teate;
	}
	public void setTeate(String teate) {
		this.teate = teate;
	}

	@Override
	public String toString() {
		return this.name;
	}
}



posted by lightbox at 2016-10-17 21:37 | Comment(0) | Android Studio 2016 | このブログの読者になる | 更新情報をチェックする

2016年10月03日


Firebase API + Android Studio : Database のデータを Java に取得する方法は3通りあります。(orderByChild 使う場合は、getChildren してから)

2016/10/03 : orderByChild の処理 を追加しました
記事末に、@IgnoreExtraProperties アノテーションの説明を追加しました。
記事末に、『JSON データを直接使用する』もあります。

ArrayList として取得
HashMap として取得
Userクラスとして取得
全体ソース
まず、環境とプロジェクトの作成方法は、『Firebase API + Android Studio : Database 処理の基本設定』にまとめました。そちらを参照して下さい。 Firebase の Database は、JSON をイメージした格納方法になっており、データはツリー構造の末端にあると考えて、そこまでのパスを指定して読み出します。ここでは、以下のようなデータを想定しており、0、1、2、の部分は配列のインデックスを意味しています。 ArrayList として取得 users を パスとして与えて取得すると、その下は配列なので ArrayList として取得する事ができます。また、users/0 のパスで取得するデータは クラス(User)を作成して、User.class を使用して Userクラスのインスタンスに直接セットする事ができます。そして、users を パス とした場合は ArrayList<User> としても取得する事ができます。
GenericTypeIndicator<ArrayList<User>> t = new GenericTypeIndicator<ArrayList<User>>() {};

ArrayList<User> user = dataSnapshot.getValue(t);
Iterator<User> it = user.iterator();
while( it.hasNext() ) {
	User work = it.next();
	Log.i("lightbox", work.getCode() );
	Log.i("lightbox", work.getName() );
}


DataSnapshot dataSnapshot は、Database のデータを取得する onDataChange イベントのパラメータとして使用可能なもので、この中にデータがセットされています。

そして、ArrayList は 以下のようにして配列に変換して利用してもいいでしょう。
User[] users = user.toArray(new User[0]);

for( int i = 0; i < users.length; i++){
	Log.i("lightbox", users[i].getCode() );
	Log.i("lightbox", users[i].getName() );
}


HashMap として取得

User クラスを作成しない場合、users を パスとした場合の一般的な方法として ArrayList<HashMap<String,Object> として取得する事ができます。
GenericTypeIndicator<ArrayList<HashMap<String,Object>>> t2
	= new GenericTypeIndicator<ArrayList<HashMap<String,Object>>>() {};

ArrayList<HashMap<String,Object>> useList = dataSnapshot.getValue(t2);

Iterator<HashMap<String,Object>> it2 = useList.iterator();
while( it2.hasNext()) {
	HashMap<String,Object> userMap = it2.next();
	Log.i( "lightbox", userMap.get("code").toString() );
	Log.i( "lightbox", userMap.get("name").toString() );
}


Userクラスとして取得
User user = dataSnapshot.getValue(User.class);

TextView tv1 = (TextView) MainActivity.this.findViewById(R.id.textCode);
tv1.setText(user.getCode());
TextView tv2 = (TextView) MainActivity.this.findViewById(R.id.textName);
tv2.setText(user.getName());

※ クラスで定義するフィールドの整数は long で定義して下さい。(関連 API ドキュメント情報)

クラス内の変数は、public のフィールドで定義してもかまいませんし、setter / getter で定義してもかまいません。排除したいフィールドは、@Exclude アノテーションを使用できます。

また、クラスには引数の無い処理の無いデフォルトコンストラクタを定義しておいて下さい。以下のような但し書きがあります
Default constructor required for calls to DataSnapshot.getValue(User.class)
※ 概要印刷用 PDF ※ ソース全体印刷用 PDF データを取得する為の addListenerForSingleValueEvent ドキュメントには、『Read data once』とタイトルして解説されていますが、必要な時にデータを取得する為のイベントの登録処理です。他のリスナーは、状態が変化した時に取得可能なもので、上のリンク先のページの『Listen for events』で解説されています。 addListenerForSingleValueEvent は、一般的にサーバよりデータを取得するメソッドとして使用され、この中のイベントで DataSnapshot を使ってデータにアクセスします。 3種類の取得パターンの全体ソース
public class MainActivity extends AppCompatActivity {

	private FirebaseDatabase database;
	private DatabaseReference mDatabase;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);

		database = FirebaseDatabase.getInstance();
		mDatabase = database.getReference();

		Button readButton = (Button) MainActivity.this.findViewById(R.id.readButton);
		readButton.setOnClickListener(new View.OnClickListener() {
			@Override
			public void onClick(View v) {

				mDatabase.addListenerForSingleValueEvent(new ValueEventListener() {
					@Override
					public void onDataChange(DataSnapshot dataSnapshot) {
						if ( dataSnapshot.exists() ) {
							Log.i("lightbox", dataSnapshot.getValue().toString());
						}
					}

					@Override
					public void onCancelled(DatabaseError databaseError) {

					}
				});


				mDatabase.child("users").addListenerForSingleValueEvent(new ValueEventListener() {
					@Override
					public void onDataChange(DataSnapshot dataSnapshot) {
						if ( dataSnapshot.exists() ) {
							Log.i("lightbox", "-- users の状態");
							Log.i("lightbox", dataSnapshot.getValue().toString());
							Log.i("lightbox", "-- ArrayList<User>");

							GenericTypeIndicator<ArrayList<User>> t = new GenericTypeIndicator<ArrayList<User>>() {};
							ArrayList<User> user = dataSnapshot.getValue(t);
							Iterator<User> it = user.iterator();
							while( it.hasNext() ) {
								User work = it.next();
								Log.i("lightbox", work.getCode() );
								Log.i("lightbox", work.getName() );
							}

							Log.i("lightbox", "-- 配列");

							User[] users = user.toArray(new User[0]);
							for( int i = 0; i < users.length; i++){
								Log.i("lightbox", users[i].getCode() );
								Log.i("lightbox", users[i].getName() );
							}

							Log.i("lightbox", "-- ArrayList<HashMap<String,Object>>");

							GenericTypeIndicator<ArrayList<HashMap<String,Object>>> t2
								= new GenericTypeIndicator<ArrayList<HashMap<String,Object>>>() {};
							ArrayList<HashMap<String,Object>> useList = dataSnapshot.getValue(t2);
							Iterator<HashMap<String,Object>> it2 = useList.iterator();
							while( it2.hasNext()) {
								HashMap<String,Object> userMap = it2.next();
								Log.i( "lightbox", userMap.get("code").toString() );
								Log.i( "lightbox", userMap.get("name").toString() );
							}
						}
						else {
							Log.i("lightbox","データを読み込めませんでした");
						}
					}

					@Override
					public void onCancelled(DatabaseError databaseError) {
						Log.i("lightbox","onCancelled");
						StringWriter sw = new StringWriter();
						PrintWriter pw = new PrintWriter(sw);
						databaseError.toException().printStackTrace(pw);
						pw.flush();
						String stackTrace = sw.toString();
						Log.i("lightbox",stackTrace);

					}
				});

			}
		});

		Button readUserButton = (Button) MainActivity.this.findViewById(R.id.readUserButton);
		readUserButton.setOnClickListener(new View.OnClickListener() {
			@Override
			public void onClick(View v) {

				mDatabase.child(String.format("users/%d",0)).addListenerForSingleValueEvent(new ValueEventListener() {
					@Override
					public void onDataChange(DataSnapshot dataSnapshot) {
						if ( dataSnapshot.exists() ) {
							Log.i("lightbox", dataSnapshot.getValue().toString());

							User user = dataSnapshot.getValue(User.class);

							TextView tv1 = (TextView) MainActivity.this.findViewById(R.id.textCode);
							tv1.setText(user.getCode());
							TextView tv2 = (TextView) MainActivity.this.findViewById(R.id.textName);
							tv2.setText(user.getName());
						}
					}

					@Override
					public void onCancelled(DatabaseError databaseError) {

					}
				});

			}
		});


	}

	public static class User {

		private String code;
		private String name;

		public User() {}

		public User(String code, String name) {
			this.code = code;
			this.name = name;
		}

		public String getCode() {
			return code;
		}
		public String getName() {
			return name;
		}
		public void setCode(String code) {
			this.code = code;
		}
		public void setName(String name) {
			this.name = name;
		}

	}

}


orderByChild の処理

ツリーが 配列では無くHashMap で構成されているデータは、直接 HashMap に格納できますが、順序が保証されないので、orderByChild メソッドを使用してデータをソートしてから取得します。その場合は、DataSnapshot から getChildren メソッドで一覧を取得する必要があります
				mDatabase.child("class").orderByChild("code").addListenerForSingleValueEvent(new ValueEventListener() {
					@Override
					public void onDataChange(DataSnapshot dataSnapshot) {
						if ( dataSnapshot.exists() ) {

							userList = new ArrayList<User>();

							Iterator<DataSnapshot> child = dataSnapshot.getChildren().iterator();
							while(child.hasNext()) {
								DataSnapshot next = child.next();
								User user = next.getValue(User.class);

								userList.add(user);
							}


						}
						else {
							Log.i("lightbox","データを読み込めませんでした");
						}
					}

					@Override
					public void onCancelled(DatabaseError databaseError) {
						Log.i("lightbox","onCancelled");

					}
				});


※ 印刷用 PDF
※ 印刷用 PDF(Module Gradle)

@IgnoreExtraProperties アノテーションについて



結局付けても付けなくても直接の動作には影響ありません。ただ、付けない場合はログにワーニングが表示され、付けるとワーニングは表示されません。

これは、開発中に Database 側にあって、クラス側に無いデータをクラスを使って読み込んだ時に確認できる内容になると思います。

Firebase ドキュメント : Update your Java model objects

@Exclude アノテーションについて

上のリンク先では、@Exclude についても記述されています。これを使用すると、意図的にそのフィールドを排除できます。サンプルでは、public なフィールドに設定されていますが、setter/getter に対して指定できます。

補足 : JSON データを直接使用する

データベースのルールが ".read": true になっている場合は、Firebase のプロジェクトの Database の URL で直接 JSON 文字列を使用できます。ルートは https://プロジェクトid.firebaseio.com/.json で取得でき、他のパスは、.json を拡張子として付加すると取得できます。
※ ?print=pretty というパラメータを指定できます(整形します)

▼ 公開サンプル
https://json-sample-b69b7.firebaseio.com/.json?print=pretty
https://json-sample-b69b7.firebaseio.com/users/0.json
▼ JSONLint にて整形
http://jsonlint.com/?json=https://json-sample-b69b7.firebaseio.com/.json

また、このアクセスは、Access-Control-Allow-Origin:* を返すので、jQuery で簡単に利用可能です。(ルールが要ログインの場合は、アクセストークンが必要になり、Android だけで利用するのは結構面倒になります)

関連する記事

Firebase API + Android Studio : Database にデータを保存は単純で、DatabaseReference の setValue メソッドを使用します

Firebase API + Android Studio : Database 処理の基本設定

Android Studio 2.2 で新規プロジェクトを作成すると『Could not reserve enough space for 1572864KB object heap』というエラーが出る場合の対処方法



posted by lightbox at 2016-10-03 19:04 | Comment(0) | Android Studio 2016 | このブログの読者になる | 更新情報をチェックする

2016年09月27日


Android : Data Binding + Firebase API で ListView にデータを表示する

ListView の扱いとしては、『Android : Data Binding で ListView へのデータ表示を凄く簡単にする』 の拡張です。

Firebase 部分のセットアップは、『Firebase API + Android Studio : Database 処理の基本設定』を参照して下さい。

Firebase API で Databaseデータ を読み込む方法に関しては『Firebase API + Android Studio : Database のデータを Java に取得する方法は3通りあります。』を参照して下さい

Firebase の ArrayList データを使用したソース
public class MainActivity extends Activity {

	private FirebaseDatabase database;
	private DatabaseReference mDatabase;
	private ArrayList<Item> users;
	private User user;

	TestArrayAdapter<Item> adapter = null;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);

		database = FirebaseDatabase.getInstance();
		mDatabase = database.getReference();

		ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
		user = new User();
		user.setFirstName("山田");
		user.setLastName("タロウ");
		binding.setUser(user);

		adapter = new TestArrayAdapter<Item>(
				MainActivity.this,
				R.layout.myitem,
				new TestArrayAdapter.OnGetViewListener() {
					@Override
					public View onGetViewListener(int position, View convertView, ViewGroup parent) {

						MyitemBinding myitem;

						if ( convertView == null ) {
							LayoutInflater inflater = (LayoutInflater) MainActivity.this.getSystemService
								(Context.LAYOUT_INFLATER_SERVICE);
							myitem = DataBindingUtil.inflate(inflater, R.layout.myitem, parent, false);
						}
						else {
							myitem = DataBindingUtil.getBinding(convertView);
						}


						ListView lv = (ListView)parent;
						TestArrayAdapter taa = (TestArrayAdapter)lv.getAdapter();
						myitem.setItem(  taa.getItem(position) );

						// ここが重要です
						return myitem.getRoot();
					}
				}
		);

		((ListView)MainActivity.this.findViewById(R.id.listView)).setAdapter(adapter);

		// Firebase Database より読み込み
		MainActivity.this.findViewById(R.id.buttonFirebase).setOnClickListener(new View.OnClickListener() {
			@Override
			public void onClick(View v) {

				mDatabase.child("users").addListenerForSingleValueEvent(new ValueEventListener() {
					@Override
					public void onDataChange(DataSnapshot dataSnapshot) {
						if ( dataSnapshot.exists() ) {
							Log.i("lightbox", dataSnapshot.getValue().toString());

							GenericTypeIndicator<ArrayList<Item>> t = new GenericTypeIndicator<ArrayList<Item>>() {};
							users = dataSnapshot.getValue(t);

							adapter.clear();
							adapter.addAll(users);

						}
						else {
							Log.i("lightbox","データを読み込めませんでした");
						}
					}

					@Override
					public void onCancelled(DatabaseError databaseError) {
						Log.i("lightbox","onCancelled");
						StringWriter sw = new StringWriter();
						PrintWriter pw = new PrintWriter(sw);
						databaseError.toException().printStackTrace(pw);
						pw.flush();
						String stackTrace = sw.toString();
						Log.i("lightbox",stackTrace);

					}
				});
			}
		});

		// ボタンを押して、オブジェクトの変更のみで反映されます
		MainActivity.this.findViewById(R.id.buttonSetValue).setOnClickListener(new View.OnClickListener() {
			@Override
			public void onClick(View v) {
				user.setFirstName("鈴木");
				user.setLastName("ジロウ");
				if ( users != null ) {
					users.get(0).setText("getView での記述が簡単になりました");
					users.get(1).setText("しかも、オブジェクトを変更すると ListView も変わります");
				}
			}
		});
	}

	// バインド用クラス(1)
	public static class User extends BaseObservable {
		private String firstName;
		private String lastName;

		@Bindable
		public String getFirstName() {
			return this.firstName;
		}
		@Bindable
		public String getLastName() {
			return this.lastName;
		}

		public void setFirstName(String firstName) {
			this.firstName = firstName;
			notifyPropertyChanged(sample.lightbox.myapplication.BR.firstName);
		}
		public void setLastName(String lastName) {
			this.lastName = lastName;
			notifyPropertyChanged(sample.lightbox.myapplication.BR.lastName);
		}
	}

	// バインド用クラス(2) : ListView 用
	public static class Item extends BaseObservable {
		private String title;
		private String text;
		private boolean flg = false;

		public Item() {}

		public Item(String text,String title) {
			this.text = text;
			this.title = title;
		}

		@Bindable
		public String getText() {
			return this.text;
		}
		@Bindable
		public String getTitle() {
			return this.title;
		}

		public void setText(String text) {
			this.text = text;
			notifyPropertyChanged(sample.lightbox.myapplication.BR.text);
		}

		public void seTitle(String title) {
			this.title = title;
			notifyPropertyChanged(sample.lightbox.myapplication.BR.title);
		}

		public String getCode() {
			return title;
		}
		public String getName() {
			return text;
		}
		public void setCode(String code) {
			this.title = code;
		}
		public void setName(String name) {
			this.text = name;
		}

	}

}

Item クラスに、Firebase で定義したデータを使用する為に、Firebase 用の Setter を作成して、元々のデータ処理と互換性を持たせています。

setText と setTitle 側で、notifyPropertyChanged を使っているので、そちらでデータの変更を行っています。

このデータは、以下のようなフォーマットであり、ArrayList として直接取得しています。



しかし、個別のデータとしては上記データは特殊な部類になるので、以下のようなデータで ListView にデータをセットします。



Firebase の HashMap データを使用したソース
public class MainActivity extends Activity {

	private FirebaseDatabase database;
	private DatabaseReference mDatabase;
	private HashMap<String,Item> users;
	private ArrayList<Item> userList;
	private User user;

	TestArrayAdapter<Item> adapter = null;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);

		database = FirebaseDatabase.getInstance();
		mDatabase = database.getReference();

		ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
		user = new User();
		user.setFirstName("山田");
		user.setLastName("タロウ");
		binding.setUser(user);

		adapter = new TestArrayAdapter<Item>(
				MainActivity.this,
				R.layout.myitem,
				new TestArrayAdapter.OnGetViewListener() {
					@Override
					public View onGetViewListener(int position, View convertView, ViewGroup parent) {

						MyitemBinding myitem;

						if ( convertView == null ) {
							LayoutInflater inflater = (LayoutInflater) MainActivity.this.getSystemService
								(Context.LAYOUT_INFLATER_SERVICE);
							myitem = DataBindingUtil.inflate(inflater, R.layout.myitem, parent, false);
						}
						else {
							myitem = DataBindingUtil.getBinding(convertView);
						}

						ListView lv = (ListView)parent;
						TestArrayAdapter taa = (TestArrayAdapter)lv.getAdapter();
						myitem.setItem(  taa.getItem(position) );

						// ここが重要です
						return myitem.getRoot();
					}
				}
		);

		((ListView)MainActivity.this.findViewById(R.id.listView)).setAdapter(adapter);

		// Firebase Database より読み込み
		MainActivity.this.findViewById(R.id.buttonFirebase).setOnClickListener(new View.OnClickListener() {
			@Override
			public void onClick(View v) {

				mDatabase.child("class").addListenerForSingleValueEvent(new ValueEventListener() {
					@Override
					public void onDataChange(DataSnapshot dataSnapshot) {
						if ( dataSnapshot.exists() ) {
							Log.i("lightbox", dataSnapshot.getValue().toString());

							GenericTypeIndicator<HashMap<String,Item>> t = new GenericTypeIndicator<HashMap<String,Item>>() {};
							users = dataSnapshot.getValue(t);

							// 必要ならば、ここでデータ順序の変更が必要です
							userList = new ArrayList<Item>(users.values());

							adapter.clear();
							adapter.addAll(userList);

						}
						else {
							Log.i("lightbox","データを読み込めませんでした");
						}
					}

					@Override
					public void onCancelled(DatabaseError databaseError) {
						Log.i("lightbox","onCancelled");
						StringWriter sw = new StringWriter();
						PrintWriter pw = new PrintWriter(sw);
						databaseError.toException().printStackTrace(pw);
						pw.flush();
						String stackTrace = sw.toString();
						Log.i("lightbox",stackTrace);

					}
				});
			}
		});

		// ボタンを押して、オブジェクトの変更のみで反映されます
		MainActivity.this.findViewById(R.id.buttonSetValue).setOnClickListener(new View.OnClickListener() {
			@Override
			public void onClick(View v) {
				user.setFirstName("鈴木");
				user.setLastName("ジロウ");
				if ( userList != null ) {
					userList.get(0).setText("getView での記述が簡単になりました");
					userList.get(1).setText("しかも、オブジェクトを変更すると ListView も変わります");
				}
			}
		});
	}

	// バインド用クラス(1)
	public static class User extends BaseObservable {
		private String firstName;
		private String lastName;

		@Bindable
		public String getFirstName() {
			return this.firstName;
		}
		@Bindable
		public String getLastName() {
			return this.lastName;
		}

		public void setFirstName(String firstName) {
			this.firstName = firstName;
			notifyPropertyChanged(sample.lightbox.myapplication.BR.firstName);
		}
		public void setLastName(String lastName) {
			this.lastName = lastName;
			notifyPropertyChanged(sample.lightbox.myapplication.BR.lastName);
		}
	}

	// バインド用クラス(2) : ListView 用
	public static class Item extends BaseObservable {
		private String title;
		private String text;
		private boolean flg = false;

		public Item() {}

		public Item(String text,String title) {
			this.text = text;
			this.title = title;
		}

		@Bindable
		public String getText() {
			return this.text;
		}
		@Bindable
		public String getTitle() {
			return this.title;
		}

		public void setText(String text) {
			this.text = text;
			notifyPropertyChanged(sample.lightbox.myapplication.BR.text);
		}

		public void seTitle(String title) {
			this.title = title;
			notifyPropertyChanged(sample.lightbox.myapplication.BR.title);
		}

		public String getCode() {
			return title;
		}
		public String getName() {
			return text;
		}
		public void setCode(String code) {
			this.title = code;
		}
		public void setName(String name) {
			this.text = name;
		}

	}

}




posted by lightbox at 2016-09-27 20:36 | Comment(0) | Android Studio 2016 | このブログの読者になる | 更新情報をチェックする

2016年09月26日


Firebase API + Android Studio : Database 処理の基本設定

Firebase のデータベースは、JSON データです。読み込みはイベントを使用し、保存は API の呼び出しで直接行います。

具体的には、DatabaseReference のスーパークラス である Query の addListenerForSingleValueEvent 経由で DataSnapshot を取得してその中からデータを取得します。

読込み記事
Firebase API + Android Studio : Database のデータを Java に取得する方法は3通りあります。

保存は、DatabaseReference のメソッドを使用する事によって完結します。もちろん、更新された事のイベントを取得はできますが、最初のアプローチとしてはこの二つを設定するだけでいいと思います。

書込み記事
Firebase API + Android Studio : Database にデータを保存は単純で、DatabaseReference の setValue メソッドを使用します

Android Studio に必要なもの

1) Google Play services を SDK マネージャからインストールしておきます。



2) Google APIs を含む System Image を SDK マネージャからインストールしておきます。

実機であれば必要ありませんが、エミュレータから実行する場合に必要です。この System Image を使用して AVD を作成しておく必要があります。


※ 経験則上、Use Host GPU をチェックしておくと、エミュレータを正しく回転してくれます。
※ 時々インストールしたアプリは削除したほうがいいです。

3) Android Studio の Settings で、Instant Run を無効にする

▼ 2.2 がリリースされたので、アップデートした場合は必要ないです


これは、Android Studio 2.2 以降で使用可能な機能の絡みで、Database 機能に直接は関係無いようですが コアなライブラリと関係しているようなので設定しておきます。

▼ 関連
Android Studio 2.2 で新規プロジェクトを作成すると『Could not reserve enough space for 1572864KB object heap』というエラーが出る場合の対処方法

※ 手順確認用 PDF 

Android Studio プロジェクトの準備手順

Firebase のドキュメントページで、『Install the Firebase SDK.』という記述が出て来ますが、これは プロジェクトレベルの Gradle に 『classpath 'com.google.gms:google-services:3.0.0'』を追加する事を指します。また、目的の API によって Module レベルの Gradle にライブラリを追加する作業が必要です。

1) Firebase コンソールで プロジェクトを作成

Firebase でログインすると、右上に『コンソールへ移動』とあるのでクリックして移動します。そこで『新規プロジェクトを作成』からプロジェクトを作成します。

2) Android Studio に Firebase 用の新しいプロジェクトを作成

Firebase に『新しいアプリ』を作成して、Android Studio と関係付ける必要があるのですが、Firebase 側の登録処理で、Android Studio のパッケージ名が必要になります。

3) Android アプリに Firebase を追加

作業的には、Firebase に追加するのですが、目的として 『Android アプリから参照可能な Firebase API を追加する作業』という意味になります。Android Studio のパッケージ名を入力して進めると、google-services.json のダウンロードが始まるので、Android Studio で作成したプロジェクトの app フォルダ内へダウンロードします。



4) プロジェクト build.gradle と app build.gradle の変更


▼ クリックするとクリップボードへコピーします


上が、『Install the Firebase SDK』です。この画像には、Module(アプリ) レベルの変更として apply plugin について書いてありますが、これは最後の行です。つまりは、以下を参考にして下さい。
apply plugin: 'com.android.application'

android {
    compileSdkVersion 22
    buildToolsVersion "23.0.3"

    defaultConfig {
        applicationId "lightbox.sep.fire3"
        minSdkVersion 19
        targetSdkVersion 22
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    testCompile 'junit:junit:4.12'
    compile 'com.android.support:appcompat-v7:22.2.1'
    compile 'com.google.firebase:firebase-core:9.4.0'
    compile 'com.google.firebase:firebase-database:9.4.0'
}

apply plugin: 'com.google.gms.google-services'
※ 印刷用 PDF

firebase のライブラリとして core と database を追加しています。使用する処理によっていろいろなライブラリが用意されており、こちらから一覧を参照できます。


5) AndroidManifest の変更

インターネットアクセスが必要なので、android.permission.INTERNET を追加しておきます。

6) Firebase Database のセキュリティ設定の変更

デフォルトでは、ログイン処理をしないとアクセスできないので、最初は無条件にアクセス可能にしておきます。ルールタブから以下のように変更して下さい。



7) データの作成(サンプル)

以下のデータは、JSON 的には配列データです。Firebase では、表現上以下のようになります。途中のインデックスが飛んでしまうと、そこは null のデータが設定された事になり、インデックスとしては存在する事になります。



作成後、右上のメニューからエクスポートとして内容を確認して下さい。
{
  "users" : [ {
    "code" : "0001",
    "name" : "浦岡 友也"
  }, {
    "code" : "0002",
    "name" : "山村 洋代"
  }, {
    "code" : "0003",
    "name" : "多岡 冬行"
  } ]
}


※ 手順確認用 PDF


関連する記事

Firebase API + Android Studio : Database のデータを Java に取得する方法は3通りあります。

Firebase API + Android Studio : Database にデータを保存は単純で、DatabaseReference の setValue メソッドを使用します

Android Studio 2.2 で新規プロジェクトを作成すると『Could not reserve enough space for 1572864KB object heap』というエラーが出る場合の対処方法





posted by lightbox at 2016-09-26 15:30 | Comment(0) | Android Studio 2016 | このブログの読者になる | 更新情報をチェックする
Seesaa の各ページの表示について
Seesaa の 記事がたまに全く表示されない場合があります。その場合は、設定> 詳細設定> ブログ設定 で 最新の情報に更新の『実行ボタン』で記事やアーカイブが最新にビルドされます。

Seesaa のページで、アーカイブとタグページは要注意です。タグページはコンテンツが全く無い状態になりますし、アーカイブページも歯抜けページはコンテンツが存在しないのにページが表示されてしまいます。

また、カテゴリページもそういう意味では完全ではありません。『カテゴリID-番号』というフォーマットで表示されるページですが、実際存在するより大きな番号でも表示されてしまいます。

※ インデックスページのみ、実際の記事数を超えたページを指定しても最後のページが表示されるようです

対処としては、このようなヘルプ的な情報を固定でページの最後に表示するようにするといいでしょう。具体的には、メインの記事コンテンツの下に『自由形式』を追加し、アーカイブとカテゴリページでのみ表示するように設定し、コンテンツを用意するといいと思います。


※ エキスパートモードで表示しています

アーカイブとカテゴリページはこのように簡単に設定できますが、タグページは HTML 設定を直接変更して、以下の『タグページでのみ表示される内容』の記述方法で設定する必要があります

<% if:page_name eq 'archive' -%>
アーカイブページでのみ表示される内容
<% /if %>

<% if:page_name eq 'category' -%>
カテゴリページでのみ表示される内容
<% /if %>

<% if:page_name eq 'tag' -%>
タグページでのみ表示される内容
<% /if %>
この記述は、以下の場所で使用します


Windows
container 終わり

フリーフォントで簡単ロゴ作成
フリーフォントでボタン素材作成
フリーフォントで吹き出し画像作成
フリーフォントではんこ画像作成
ほぼ自由に利用できるフリーフォント
フリーフォントの書体見本とサンプル
画像を大きく見る為のウインドウを開くボタンの作成

Android SDK ポケットリファレンス
改訂版 Webデザイナーのための jQuery入門
今すぐ使えるかんたん ホームページ HTML&CSS入門
CSS ドロップシャドウの参考デモ
PHP正規表現チェッカー
Google Hosted Libraries
cdnjs
BUTTONS (CSS でボタン)
イラストAC
ぱくたそ
写真素材 足成
フリーフォント一覧
utf8 文字ツール
右サイド 終わり
base 終わり