20200916のAndroidに関する記事は3件です。

5. 【Android/Kotlin】ダイアログ(Dialog)

はじめに

DreamHanksのMOONです。

前回は「リストビュー」というViewについて説明をしました。
4. 【Android/Kotlin】リストビュー(ListView)

今回はリストビューのアイテムをクリックした場合、
ダイアログが表示されるし、リストのアイテムが削除することができることをしていきます。

ダイアログとは

ダイアログは、ユーザーによる意思決定や追加情報の入力用に表示される小さなウィンドウです。ダイアログは全画面に表示されることはなく、通常はユーザーが処理を続ける前にアクションを起こす必要があるモデルイベントに使用されます。

種類は代表的にAlertDialogDatePickerDialogTimePickerDialogがあります。
その中に今回はAlertDailogを使用していきます。

AlertDialog追加

前回に作成したコードを使用して修正します。
全体コードを確認したい場合は前回のリンクで確認してください。

まず、Activityファイルを下記とように修正します。

ListViewActivity.kt
package com.example.practiceapplication

import android.app.AlertDialog
import android.content.DialogInterface
import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log.d
import android.view.ContextThemeWrapper
import android.widget.*
import kotlinx.android.synthetic.main.activity_listview.*
import javax.xml.validation.Validator

class ListViewActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_listview)

        var userList = arrayListOf<User>(
            User("佐藤","sato@ggggg.com"),
            User("鈴木","suzuki@aaaaa.com"),
            User("高橋","takahasi@yyyyy.com"),
            User("伊藤","ito@fffff.com"),
            User("渡辺","watanabe@bbbbb.com"),
            User("山本","yamamoto@zzzzz.com"),
            User("中村","nakamura@ccccc.com"),
            User("小林","kobayasi@xxxxx.com"),
            User("加藤","gato@wwwww.com")
        )

        var list_view = findViewById<ListView>(R.id.list_view)
        //リストビューのアイテムを変更
        setListView(userList)

        //リストビューのアイテムのクリックイベントを設定
        list_view.setOnItemClickListener { parent, view, position, id ->
            //クリックされたアイテム
            val element = parent.getItemAtPosition(position)

            //ダイアログを生成
            val builder = AlertDialog.Builder(ContextThemeWrapper(this@ListViewActivity, R.style.Theme_AppCompat_Light_Dialog))
            builder.setTitle("ユーザー名")
            //選択されたアイテムがユーザーオブジェクトの場合
            if(element is User){
                //ダイアログの内容にユーザー名を設定
                builder.setMessage(element.name)
            }
            //確認ボタンを追加(確認ボタンは単純に確認機能)
            builder.setPositiveButton("確認", DialogInterface.OnClickListener { dialog, which ->
            })
            //削除ボタンを追加
            builder.setNegativeButton("削除", DialogInterface.OnClickListener { dialog, which ->
                //ユーザーリストで選択されたアイテムを削除
                userList.remove(element)

                //リストビューのアイテムを変更
                setListView(userList)
            })

            builder.show()
        }
    }

    fun setListView(userList: ArrayList<User>){
        //アダプターにリストを設定
        val Adapter = ListAdapter(this, userList)
        //リストビューにアダプターを設定
        list_view.adapter = Adapter
    }
}

追加分

 ・リストビューのアイテムクリックイベント設定

        //リストビューのアイテムのクリックイベントを設定
        list_view.setOnItemClickListener { parent, view, position, id ->
            //クリックされたアイテム
            val element = parent.getItemAtPosition(position)

            //ダイアログを生成
            val builder = AlertDialog.Builder(ContextThemeWrapper(this@ListViewActivity, R.style.Theme_AppCompat_Light_Dialog))
            builder.setTitle("ユーザー名")
            //選択されたアイテムがユーザーオブジェクトの場合
            if(element is User){
                //ダイアログの内容にユーザー名を設定
                builder.setMessage(element.name)
            }
            //確認ボタンを追加(確認ボタンは単純に確認機能)
            builder.setPositiveButton("確認", DialogInterface.OnClickListener { dialog, which ->
            })
            //削除ボタンを追加
            builder.setNegativeButton("削除", DialogInterface.OnClickListener { dialog, which ->
                //ユーザーリストで選択されたアイテムを削除
                userList.remove(element)

                //リストビューのアイテムを変更
                setListView(userList)
            })

            builder.show()
        }

 ・リストビューにアダプターを設定

    fun setListView(userList: ArrayList<User>){
        //アダプターにリストを設定
        val Adapter = ListAdapter(this, userList)
        //リストビューにアダプターを設定
        list_view.adapter = Adapter
    }

①リストビューにアイテムクリックイベントを設定します。
②イベント内にダイアログを生成します。
③ダイアログにタイトルを設定、メッセージには選択されたアイテムの名前を設定します。
④確認ボタンを設定し、確認ボタンは別の設定はしなくても大丈夫です。
⑤削除ボタンを設定し、削除ボタンにはユーザーリスト内のユーザー中に選択されたユーザーを削除します。
⑥最後にリストビューに変更されたユーザーにストのアダプターを設定します。

アプリ起動

・初期画面

・佐藤さんをクリックした場合

・佐藤さんから伊藤まで削除した場合

終わりに

今回は「ダイアログ」について説明をしていきました。

次回は「スピナー」について説明をしていきます。
6. 【Android/Kotlin】スピナー(Spinner)

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AABから単一APKを作成する

AABファイルは多デバイスの情報が含まれており、インストール時にいい感じの構成にしてくれるいかしたやつです。これはGooglePlayStoreで配布するときは問題ないですが、AppDistributionで配布する場合にAPKにしか対応していないので全てのデバイス情報を含んだAPKファイルを作成する必要が出てきます。

aabファイルをapkファイルに変更する

bundleToolsのダウンロード

ここ からダウンロード

bundleToolsから単一APKを作成

--mode=universal でAPKのセットを生成するとデバイスごとではない単一APKが生成される

java -jar bundletool-all-1.2.0.jar build-apks \
 --bundle=app.aab \
 --output=app.apks \
 --ks=keystore \
 --ks-pass=pass:'password' \
 --ks-key-alias='alias' \
 --key-pass=pass:'password' \
 --mode=universal

apksを解凍

出来上がったapksファイルはzipファイルなので回答することでapkファイルが出力される。

unzip app.apks

終わり

ということをやれば、全デバイス対応したAPKが出来上がりますが、参考の注意書きに書いてあるように、含まれないモジュールが出てくる可能性があります。

注: bundletool は、マニフェストに <dist:fusing dist:include="true"/> の指定がある機能モジュールのみをユニバーサル APK に含めます。詳細については、機能モジュールのマニフェストをご覧ください。

AABとAPKのビルドを2回流すのは面倒だからとAABからAPKを作るぐらいなら、APKのビルドを流して作るほうが無難ではあります。

参考

https://developer.android.com/studio/command-line/bundletool?hl=ja

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

FlutterでiOS、Android両方で動くカメラアプリを作る

Flutterを使ってマルチプラットフォームなカメラアプリを作っていく

「iOSでもAndroidでも動くアプリで、」
「スマホ内の画像にアクセスしてローカルのDBに保存可能」
「アプリ内でもカメラを起動できる」

っていう要件をFlutterで満たせるかの検証をバイト先に任されたからなー

Flutter

iOS、Androidといった異なるプラットフォームのアプリを一つのコードで書くことができるGoogle製のオープンソースのクロスプラットフォームのフレームワーク

公式ドキュメントも充実・サードパーティー製のプラグインも日々増えているので、旬な技術だと思っている

公式Youtubeにはお洒落な感じデザインのやらWidgetやらの説明動画がたくさんあるので楽しい

環境構築は以下のサイトを参考にした。ありがとうございました

ホットリロードが神すぎる

早速作っていきたいのだけど、Flutterは言語はdartでWidget単位でコードを完成させてアプリを作っていく

公式ドキュメントでもFlutterではWidgetが全てだよって言ってる

In Flutter, “everything is a widget”! - Flutter documentation

特に重要なWidgetが Stateless WidgetStatefull Widget の2つで順番におさらいしていく

そのあと、画像ファイル・カメラへのアクセスを可能にするプラグインであるimage_picker と、データベースを扱うためのプラグインsqfliteの紹介に入って、最後にコードを説明して終わり

Stateless Widget

名前の通り状態を持たないWidget。

以下は公式YouTubeで取り上げられているコード例

class ItemCount extends StatelessWidget {
 final String name;
 final int count;

 ItemCount({this.name, this.count});

 @overide
 Widget build(BuildContext context) {
  return Text('$name: $count')
 }
}

String型のnameとint型のcountを与えると、それらをTextWidgetに渡して表示させるようなWidgetである。

Statefull Widget

状態をもつWidgetで、Stateが更新されたときに内容が更新される。

先ほどのコードをStatefulにすると以下のような感じになる。

class ItemCount extends StatefulWidget {
 final String name;

 ItemCounter({this.name});

 @override
 _ItemCounterState createState() => _ItemCounterState();
}

class _ItemCounterState extends State<ItemCounter> {
 int count = 0;

 @overide
 Widget build(BuildContext context) {
   return GestureDetector(
     onTap: () {
       setState(() {
         count++;
       });
     ),
     child: Text('${widget.name}: $count'),
   );
 }
}

setStateはその中に書かれた変数が変化した時にクラスに変更を伝える役割を持っている

これでStatelessだった画面が、タップするとcountが上がっていくプログラムになった

image_picker

image_pickerははアプリ内でカメラを立ち上げたり、スマホ内のメディア(画像)にアクセスさせたりするのに使うプラグイン

A Flutter plugin for iOS and Android for picking images from the image library, and taking new pictures with the camera. - image_picker | pub.dev

フラッター開発チームが作った純正なのでいろいろ安心

Flutterでのプラグインの導入は pubspec.yaml にプラグインの情報を追加して $ flutter pub get してあげるだけ

このプラグインではフォトライブラリへのアクセスを許可しないといけないので、iOSでは以下の変数に関する設定をios/Runner/info.plistに事前に書き込んでおく必要がある

変数名 役割
NSPhotoLibraryUsageDescription フォトライブラリへのアクセス
NSCameraUsageDescription カメラへのアクセス
NSMicrophoneUsageDescription マイクへのアクセス

動画を取り込まないなら一番下の項目は無視できる

細かい設定の仕方は公式ドキュメントをご参照ください

公式ドキュメントをサンプルのコードに手を加え、ペッとmain.dartに貼り付けたら動くものを作った

動作環境はMacBookPro上の android-x86 emulator(Pixel_3a_API_30_x86)である

例えばカメラの起動と写真の取得はたったこれだけ。

カメラの起動・写真の取得
final pickedFile = await picker.getImage(source: ImageSource.camera);

ImageSource.gallery ならフォルダへのアクセスができる

かなり使えそう。

sqflite

SQLite(データベース)を導入・利用するためのプラグイン

Flutter plugin for SQLite, a self-contained, high-reliability, embedded, SQL database engine. - sqflite | pub.dev

導入は例に漏れず pubspec.yaml にプラグインの情報を追加して $ flutter pub get してあげるだけ

公式Githubとか他の記事見ながら理解した

主な関数の説明を列挙していく

まずはデータベースへのパスを取得する必要があるらしいので、import 'package:path/path.dart'しておいて、getDatabasesPath関数でパスを取得する

それを用いると、データベースを開く処理はこんな感じでできる

openDatabase関数
String _path = join(await getDatabasesPath(), "mydb.db");
Database database = await openDatabase(_path, version: 1, onCreate: 処理);

新規にデータベースを作成してアクセスする時などは、openDatabase関数実行時にonCreateを設定すれば良い

onCreate関数は、データを事前準備しておきたい時にも使える

onCreate関数
    String _path = join(await getDatabasesPath(), "mydb.db");

    Database _database = await openDatabase(_path, version: 1,
        onCreate: (Database db, int version) async {
      await db.execute(
          "CREATE TABLE IF NOT EXISTS mydb (id INTEGER PRIMARY KEY, text TEXT)");
      // データを事前に流し込みたい時はここにinsertを記述すれば良い
    });

データベースの内容の書き換えなどで、transaction関数を利用できる

transaction関数
    await _database.transaction((txn) async {
      await txn.rawInsert('INSERT INTO mydb(text) VALUES("$textData")');
    });

レコードの取得にはrawQuery、保存にはrawInsert、更新はrawUpdate、削除はrawDeleteを使えば良い
rawQuery関数の結果はMap型のリストで返ってくる

rawQuery関数
    List<Map> _result = await _database.rawQuery('SELECT * FROM mydb');

SQLとかを発行しない処理であれば、execute関数で行える

本題

カメラとギャラリーから画像を取ってきて、DB(SQLite)に保存するアプリを作ってみた。

まずは完成したアプリをみて欲しい.

Androidエミュレーターでの動き

andq.gif

iPhoneエミュレーターでの動き

iosq.gif

Android、iphoneの両方で動作するアプリがであることが確認できる(ただし、iphoneエミュレーター上ではカメラは起動しない)

sqfliteに画像を保存する場合は画像情報をbase64でエンコードしたテキストとして写真の実体を保存する

自分はこちらを参考にした(Thank you!)

Google’s Flutter Tutorial – Save Image as String in SQLite Database

画像を保存しておくためのテーブルは以下のように定義している

  initDB() async {
    io.Directory documentsDirectory = await getApplicationDocumentsDirectory();
    String path = join(documentsDirectory.path, DB_NAME);
    var db = await openDatabase(path, version: 1, onCreate: _onCreate);
    return db;
  }

  _onCreate(Database db, int version) async {
    await db.execute("CREATE TABLE $TABLE ($ID INTEGER, $DATA TEXT)");
  }

ギャラリーからピックアップしてきた画像をBase64でエンコードして保存

  _pickImageFromGallery() {
    final picker = ImagePicker();
    picker.getImage(source: ImageSource.gallery).then((imgFile) async {
      if (imgFile != null) {
        String imgString = Utility.base64String(await imgFile.readAsBytes());
        Photo photo = Photo(0, imgString);
        dbHelper.save(photo);
      }
    });
  }

完成したコードはGitHubにおいておきます

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む