- 投稿日:2019-06-25T22:29:26+09:00
xamarin TabbedPageでタブ移動時にListViewの更新
TabbedPageにて、複数のListViewを用意し、ステータス(既読/読書中)によってリストを切り替えるインターフェースを作成した時に、タブを選択したタイミングでリストの表示内容を更新したい時がある。
そんな時は、該当タブのContentPageのAppearingイベントを利用することで実現可能だ。sample.xaml<?xml version="1.0" encoding="UTF-8"?> <TabbedPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:controls="clr-namespace:sample;assembly=books2" x:Class="sample"> <TabbedPage.Children> <ContentPage Title="既読" Padding="30" Appearing="Handle_Appearing0"> <ListView x:Name="_listView0"> <ListView.ItemTemplate> <DataTemplate> <ViewCell> <Grid> <Grid.RowDefinitions> <RowDefinition Height="1" /> <RowDefinition Height="20" /> <RowDefinition Height="40" /> <RowDefinition Height="1" /> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="1" /> <ColumnDefinition Width="50" /> <ColumnDefinition Width="*" /> <ColumnDefinition Width="1" /> </Grid.ColumnDefinitions> <Image Source="{Binding ImageL}" Grid.Row="1" Grid.Column="1" Grid.RowSpan="2" VerticalOptions="Start" Aspect="AspectFit" /> <Label Grid.Row="1" Grid.Column="2" Text="{Binding Title}" FontAttributes="Bold" /> <Label Grid.Row="2" Grid.Column="2" Text="{Binding Author}" FontSize="12" /> </Grid> </ViewCell> </DataTemplate> </ListView.ItemTemplate> </ListView> </ContentPage> <ContentPage Title="読書中" Padding="30" Appearing="Handle_Appearing1"> <ListView x:Name="_listView1"> <ListView.ItemTemplate> <DataTemplate> <ViewCell> <Grid> <Grid.RowDefinitions> <RowDefinition Height="1" /> <RowDefinition Height="20" /> <RowDefinition Height="40" /> <RowDefinition Height="1" /> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="1" /> <ColumnDefinition Width="50" /> <ColumnDefinition Width="*" /> <ColumnDefinition Width="1" /> </Grid.ColumnDefinitions> <Image Source="{Binding ImageL}" Grid.Row="1" Grid.Column="1" Grid.RowSpan="2" VerticalOptions="Start" Aspect="AspectFit" /> <Label Grid.Row="1" Grid.Column="2" Text="{Binding Title}" FontAttributes="Bold" /> <Label Grid.Row="2" Grid.Column="2" Text="{Binding Author}" FontSize="12" /> </Grid> </ViewCell> </DataTemplate> </ListView.ItemTemplate> </ListView> </ContentPage> </TabbedPage.Children> </TabbedPage>sample.xaml.csvoid Handle_Appearing0(object sender, System.EventArgs e) { // リストビューのデータを更新 _listView0.ItemsSource = wSqliteControl.GetItems(0); }引っ張り更新は実現できていたものの、Androidだとリストにデータが表示されていないと引っ張れないやんけ!!
となって悶々としてましたが、ようやく解決できました。同じような悩みを抱えている方の助けに少しでもなれば幸いです。
- 投稿日:2019-06-25T18:52:29+09:00
AppCompatSpinnerにデフォルトで指定されるstyleではまったのでメモ
概要
JavaからKotlinに書き換える中でこの2つのコンストラクタ定義でSpinnerの挙動が異なってはまったのでメモ。
public class CustomSpinner extends AppCompatSpinner { public CustomSpinner(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } public CustomSpinner(Context context, AttributeSet attrs) { super(context, attrs); } public CustomSpinner(Context context) { super(context); } }class CustomSpinnerKt @JvmOverloads constructor( context: Context, attr: AttributeSet? = null, defStyleAttr: Int = 0 ) : AppCompatSpinner(context, attr, defStyleAttr) { }原因
super(context, attrs);
では defStyleAttr にR.attr.spinnerStyle
デフォルトで適応されるのが原因だった。AppCompatSpinner.javapublic AppCompatSpinner(Context context, AttributeSet attrs) { this(context, attrs, R.attr.spinnerStyle); }
R.attr.spinnerStyle
の定義を遡っていくとv21以上ではWidget.Material.Spinner
を継承している。Widget.Material.Spinner<style name="Widget.Material.Spinner" parent="Widget.Spinner.DropDown"> <item name="background">@drawable/spinner_background_material</item> <item name="dropDownSelector">?attr/listChoiceBackgroundIndicator</item> <item name="popupBackground">@drawable/popup_background_material</item> <item name="popupElevation">@dimen/floating_window_z</item> <item name="popupAnimationStyle">@empty</item> <item name="popupEnterTransition">@transition/popup_window_enter</item> <item name="popupExitTransition">@transition/popup_window_exit</item> <item name="dropDownVerticalOffset">0dip</item> <item name="dropDownHorizontalOffset">0dip</item> <item name="overlapAnchor">true</item> <item name="dropDownWidth">wrap_content</item> <item name="popupPromptView">@layout/simple_dropdown_hint</item> <item name="gravity">start|center_vertical</item> <item name="disableChildrenWhenDisabled">true</item> </style>一方、v21未満では
Base.Widget.AppCompat.Spinner
を継承している。Base.Widget.AppCompat.Spinner<style name="Base.Widget.AppCompat.Spinner" parent="Platform.Widget.AppCompat.Spinner"> <item name="android:background">@drawable/abc_spinner_mtrl_am_alpha</item> <item name="android:popupBackground">@drawable/abc_popup_background_mtrl_mult</item> <item name="android:dropDownSelector">?attr/listChoiceBackgroundIndicator</item> <item name="android:dropDownVerticalOffset">0dip</item> <item name="android:dropDownHorizontalOffset">0dip</item> <item name="android:dropDownWidth">wrap_content</item> <item name="android:clickable">true</item> <item name="android:gravity">left|start|center_vertical</item> <item name="overlapAnchor">true</item> </style>解決策
defStyleAttr
のデフォルト引数にR.attr.spinnerStyle
を指定した。class CustomSpinnerKt @JvmOverloads constructor( context: Context, attr: AttributeSet? = null, defStyleAttr: Int = R.attr.spinnerStyle ) : AppCompatSpinner(context, attr, defStyleAttr) { }
- 投稿日:2019-06-25T15:52:26+09:00
RecyclerViewでnotifyItemInsertedやnotifyItemRemovedを使う時の注意点
背景
notifyItemInserted
やnotifyItemRemoved
を使っていると、消したいデータと違うデータが消えたり、IndexOutOfExceptionが起こってアプリがクラッシュしたので、原因を調べてみました。以下の3つを順番に実行した時の
Adapter内のリストの状態
・RecyclerViewのデータ項目の状態
・アプリ画面の状態
がどうなっているのか図で示しながら解説していきます。その後、対処法を示していきます。
- RecyclerViewを初期化した時
- リストにデータを追加した時
- notifyItemInsertedを呼び出した時
RecyclerViewを初期化した時
今回、原因を調べるために作ったAdapterクラスです。それぞれの状態だけを見ると、間違っていなさそうに見えます。
しかし、この状態で削除ボタン(R.id.deleteButton)を押すと、一度目は正しいデータが削除されますが、二度目は、違うデータが消えたり、IndexOutOfExceptionが起こります。MyRecyclerViewAdapter.ktclass MyRecyclerViewAdapter(private val context: Context, private val sampleList: MutableList<String>) : RecyclerView.Adapter<MyRecyclerViewAdapter.MyViewHolder>() { class MyViewHolder(view: View) : RecyclerView.ViewHolder(view) { val contentTextView: TextView = view.findViewById(R.id.content) val deleteButton: Button = view.findViewById(R.id.deleteButton) } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder { return MyViewHolder(LayoutInflater.from(context).inflate(R.layout.sample_list, parent, false)) } override fun getItemCount(): Int = sampleList.size override fun onBindViewHolder(holder: MyViewHolder, position: Int) { holder.contentTextView.text = "${sampleList[position]} + $position" holder.deleteButton.setOnClickListener { sampleList.removeAt(position) notifyItemRemoved(position) } } fun addItem(position: Int, string: String){ sampleList.add(position, string) } }リストにデータを追加した時
Adapter内のリストには変化がありますが、データを追加したことを通知していないため、RecyclerViewのデータ項目の状態やアプリ画面に変化はありません。
MainActivity.kt//追加するコード addButton.setOnClickListener { (recyclerView.adapter as MyRecyclerViewAdapter).addItem(1, "Z") }notifyItemInserted(position=1)を実行した時
notifyItemInserted(position: Int)
は、指定した位置に新しいデータが追加されたことを通知しています。指定した位置にあったデータ項目は元の位置 + 1
されます。追加したデータは指定した位置でバインドされます。しかし、位置がズレたデータ項目は、リバインドされないのでTextViewや削除ボタンの処理が更新されません。そのため位置2にある削除ボタンの処理を実行すると、リスト.removeAt(1)
が実行され、消したいデータと違ったデータが削除されるわけです。MainActivity.ktaddButton.setOnClickListener { (recyclerView.adapter as MyRecyclerViewAdapter).addItem(1, "Z") //追加するコード recyclerView.adapter?.notifyItemInserted(1) }対処方法
追加や削除を通知した後、notifyItemRangeChangedを使う
notifyItemRangeChanged(startPosition: Int, itemCount: Int)
は、startPosition
からitemCount
までの範囲に、データの変更があったことを通知し、リバインドしてくれます。そのため、TextViewの値や削除ボタンの処理が適したものに更新されます。MainActivity.ktaddButton.setOnClickListener { (recyclerView.adapter as MyRecyclerViewAdapter).addItem(1, "Z") recyclerView.adapter?.notifyItemInserted(1) //追加するコード recyclerView.adapter?.notifyItemRangeChanged(2, sampleList.size) }MyRecyclerViewAdapter.ktoverride fun onBindViewHolder(holder: MyViewHolder, position: Int) { holder.contentTextView.text = "${sampleList[position]} + $position" holder.deleteButton.setOnClickListener { sampleList.removeAt(position) notifyItemRemoved(position) //追加するコード notifyItemRangeChanged(position, itemCount) } }まとめ
notifyItemInserted
やnotifyItemRemoved
を使うなら、notifyItemRangeChanged
を一緒に使いましょう。
notifyDataSetChanged
を使うとどちらもやってくれますが、Documentであまりおすすめされていないのとアニメーションがされません。
もし、ここ間違っているよ等あれば、コメントで教えてください!
- 投稿日:2019-06-25T15:02:03+09:00
TextViewのEllipsizeする行数を動的に変更する
TextViewの高さが親と兄弟のレイアウトによって可変になる場合に以下のように、長すぎるテキストが見切れてしまうことがあります。
lines
とellipsize
を設定すれば良さそうですが、ビューの高さが可変なのでlines
も可変にする必要があります。
この問題は以下のようにサブクラスを定義することで解決できます。DynamicEllipsizeTextViewclass DynamicEllipsizeTextView : TextView { constructor(context: Context?) : super(context) constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int) : super( context, attrs, defStyleAttr, defStyleRes ) override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) { super.onLayout(changed, left, top, right, bottom) adjustEllipsize(bottom - top) } private fun adjustEllipsize(containerHeight: Int) { // レイアウトが確保した高さ内に収まる行数に設定 val ableToShowLineCount = containerHeight / lineHeight setLines(ableToShowLineCount) // 更新を反映するために再度テキストを設定 text = text } }
- 投稿日:2019-06-25T13:48:49+09:00
音楽ど素人が機械学習で楽譜認識するAndroidアプリを作る話 スマホで音符の検出編
はじめに
中学から吹奏楽部に入った息子が楽譜に音名を書くのがだるいと言いだしたんで、そんなもん今どきスマホとパシャッとすりゃAIでぱっと出てくるアプリがあるだろ?って言ったら意外となかったんで自分で作った
五線譜の検出編
https://qiita.com/Anadreline/items/4cf6e88c743a65b7da2c
音符検出モデルの作成編
https://qiita.com/Anadreline/items/f6c81774e241a33dd0d8
につづく第3弾、完結編です作成アプリ
Music Score Reader
https://play.google.com/store/apps/details?id=msreader.android.anadreline.com.musicscorereader
ちなみにiOS用だといくつかあります
スマホ用の楽譜認識アプリをまとめてみた
http://anadreline.blogspot.com/2019/06/wo.html開発者の初期スペック
- 五線譜のどこがドなのかがわからない
- ♯と♭って結局どっちかだけでよくない!?と思っている
- 息子にトランペットのドはドじゃないからねって言われてファッ!?ってなる
- 12音に五線譜表記って仕様がおかしくねって思っている
検証環境
この記事の内容は、以下の環境で検証しました。
- Android Studio 3.2.1
- CompileSdkVersion:27
- MinSdkVersion:21
- TargetSdkVersion:27
- OpenCV for Android 3.2.3
- TensorFlow Mobile
音符の検出プロセス
スマホ用のTensorFlow Mobileを利用して検出を行いました
ベースとなるところは公式のサンプルにすべて詰まってます
https://github.com/tensorflow/tensorflow/tree/master/tensorflow/examples/android
ただし、今やるならTensorFlow Liteを使うのがベストだと思います画像取得
まずは認識するための楽譜画像の取得
MainActivity.javaUri m_uri; Bitmap m_image; //カメラIntent呼出 String photoName = System.currentTimeMillis() + ".jpg"; ContentValues contentValues = new ContentValues(); contentValues.put(MediaStore.Images.Media.TITLE, photoName); contentValues.put(MediaStore.Images.Media.MIME_TYPE, "image/*"); m_uri = getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues); Intent intentCamera = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); intentCamera.putExtra(MediaStore.EXTRA_OUTPUT, m_uri); startActivityForResult(intentCamera, REQUEST_CAMERA); //撮影画像取得 @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (resultCode == RESULT_OK) { m_image = Utl.decodeUri(this, m_uri); } }よくあるカメラIntentからの取得、アプリではギャラリーからの取得も実装
認識画像の切り出し
楽譜の全体画像で認識をかけても大きすぎて認識できないんで
五線譜の検出編
https://qiita.com/Anadreline/items/4cf6e88c743a65b7da2c
で検出した五線譜の部分から切り出して認識用画像としました認識に必要なもの
公式のサンプルでは画像分類や物体の検出をカメラのリアルタイムプレビューで行うサンプルのため、結構なボリュームのプロジェクトになってます
今回のアプリではリアルタイムプレビューでの認識ではなく、1枚の画像から認識できればいいので、必要なものはこれだけです
公式のサンプルからClassifier.javaとTensorFlowObjectDetectionAPIModel.java
それにassetsに前回の音符検出モデルの作成編で作成したモデルファイル(frozen_inference_graph.pb)とラベルファイル(label_map.txt)の4つだけ音符の認識
TensorFlowObjectDetectionAPIModel.createで認識モデルを生成して、Classifier.recognizeImage()に認識させたい画像を放り込むと、リスト形式で認識結果が取得できる
Recognition.javaprivate static final int TF_OD_API_INPUT_SIZE = 300; private static final String TF_OD_API_MODEL_FILE = "file:///android_asset/frozen_inference_graph.pb"; Classifier detector = TensorFlowObjectDetectionAPIModel.create(getAssets(), TF_OD_API_MODEL_FILE, TF_OD_API_LABELS_FILE, TF_OD_API_INPUT_SIZE); final List<Classifier.Recognition> results = detector.recognizeImage(crop); for (final Classifier.Recognition result : results) { RectF location = result.getLocation(); //検出位置 Float conf = result.getConfidence(); //確度 String title = result.getTitle(); //ラベル名 }あとは確度の値をみて一定値以上なら採用という具合で取得
このアプリでは0.5にしてます、すこしゆるめの判断でとにかく認識率を上げようという作戦
でもまあ未検出はあるものの、誤認識はほとんどなかった
- 投稿日:2019-06-25T13:17:55+09:00
Android2.3(APIレベル9)~の対応プログラムの開発について
はじめに
こんにちは。Daddy's Officeの市川です。
先日、使わなくなったAndroidを監視カメラにするソフト「LiveCapture3 Android」を公開したのですが、そこではまったのが、古いAndroidへの対応です。
使わなくなったスマホを利用するというコンセプトなので、なるべく古いAndroidでも動くようにする必要があり、最終的には、Android2.3(APIレベル9)以降すべてで動作する方向で開発しました。
あまり凝ったUIは不要だったので、基本的にはAndroid Support Libraryを外してしまえばOKかと思ったのですが、Android6.0(APIレベル23)以降で導入された、実行時パーミッションリクエストを実装するために、Support Library V4のActivityCompatとContextCompatだけは使う必要がありました。
当然、これらを使用せずとも同様の処理は記述可能なのだと思いますが(すいません、調べていませんが、、、)、権限処理に時間を割くのは本質的ではないので、これら権限処理に必要なSupportLibraryV4だけをリンクできる方法を調査しました。
SupportLibrary V4の最小APIレベル
単純にSupportLibraryV4だけを追加すればよいと思っていたのですが、現在、SupportLibraryV4の最小APIレベルはAndroid4.0(APIレベル14)に上がっているとのこと。
バージョン サポートとパッケージ名Support Library バージョン 26.0.0(2017 年 7 月リリース)以降、すべてのサポート ライブラリ パッケージを対象に、サポートされる最小 API レベルが Android 4.0(API レベル 14)に変更されました。このため、サポート ライブラリの最近のリリースを使用する場合は、v# パッケージ表記が最小の API サポートレベルを示しているとは限りません。最近のリリースで行われたこの変更は、v4 と v7 のライブラリ パッケージでサポートされる API の最小レベルが本質的に同等であることも意味します。たとえば、support-v4 と support-v7 のどちらのパッケージも、26.0.0 リリース以降の Support Library に対して最小 API レベル 14 をサポートします。
これではminSdkVersionを14まで上げないと、Gradle Syncがエラーになってしまいます。。。
ということで、まずは、minSdkVersion 9のままでSupportLibraryV4を追加してもGradle Syncがエラーにならないようにします。
プロジェクト作成
まず、AndroidStudioで、「No Activity」で、空のプロジェクトを作成し、Gradleの内容を書き換えます。
- minSdkVerisonを9に変更
- support-v4ライブラリ以外の依存ライブラリをすべて削除
android { compileSdkVersion 28 defaultConfig { applicationId "com.daddysoffice.sample.test" minSdkVersion 9 targetSdkVersion 28 versionCode 1 versionName "1.0" } } dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation 'com.android.support:support-v4:28.0.0' }この状態でGradle Syncを実行すると、マニフェストのマージエラーになります。
ERROR: Manifest merger failed : uses-sdk:minSdkVersion 9 cannot be smaller than version 14 declared in library [com.android.support:support-v4:28.0.0] C:\Users\xxx\.gradle\caches\transforms-2\files-2.1\xxxxxxxxxxxx7f111f5\AndroidManifest.xml as the library might be using APIs not available in 9 Suggestion: use a compatible library with a minSdk of at most 9, or increase this project's minSdk version to at least 14, or use tools:overrideLibrary="android.support.v4" to force usage (may lead to runtime failures)エラーの内容は、前述のとおりで、com.android.support:support-v4:28.0.0で定義されているversion 14よりもminSdkVersion 9 は小さい、とのこと。
この解決策として提示されている最後の方法を使います。
use tools:overrideLibrary="android.support.v4" to force usage (may lead to runtime failures)tools:overrideLibrary
マニフェストファイルに、uses-sdk tools:overrideLibrary="xxxx" を記載すると、xxxxライブラリ内に記載されたminSdk/targetSdkVersionを無視することになるとのこと。
今回は、Android6.0(APIレベル23)以降のときにだけ、実行時パーミッション処理を走らせます。
APIレベル22以下の場合にはライブラリを使わないので、この最後の方法で問題ありません。早速、AndroidManifest.xmlに、この記述を追加します。
AndroidManifest.xml<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.daddysoffice.sample.broadcasttest" xmlns:tools="http://schemas.android.com/tools"> <<これと <uses-permission android:name="android.permission.CAMERA" /> <uses-sdk tools:overrideLibrary="android.support.v4/> << これを追加これで、再度Gradle Syncを走らせると、今度は下記のエラーが発生。
Manifest merger failed : uses-sdk:minSdkVersion 9 cannot be smaller than version 14 declared in library [com.android.support:support-fragment:28.0.0] C:\Users\xxxxx\.gradle\caches\transforms-2\files-2.1\xxxxxxxxxx710eb0a2\AndroidManifest.xml as the library might be using APIs not available in 9 Suggestion: use a compatible library with a minSdk of at most 9, or increase this project's minSdk version to at least 14, or use tools:overrideLibrary="android.support.fragment" to force usage (may lead to runtime failures)どうやら、supportV4をリンクすることで、いろいろとくっついてくる模様。。。
とりあえず、マニフェストのtools:overrideLibraryに、android.support.fragmentを追加して、再度Gradle Syncを実行。
すると、また別のライブラリがエラーで出てくる。。
これをエラーが出てこなくなるまで繰り返した結果、tools:overrideLibraryは、以下のようになりました。
AndroidManifest.xml<uses-sdk tools:overrideLibrary="android.support.v4,android.support.mediacompat, android.support.fragment,android.support.coreui,android.support.coreutils, android.support.v7.appcompat,android.support.graphics.drawable, android.support.loader, android.support.v7.viewpager, android.support.coordinatorlayout,android.support.drawerlayout, android.support.slidingpanelayout,android.support.customview, android.support.swiperefreshlayout, android.support.asynclayoutinflater, android.support.compat,androidx.versionedparcelable, android.arch.lifecycle,android.support.documentfile, android.support.localbroadcastmanager, android.support.print,android.support.interpolator, android.support.cursoradapter,android.arch.lifecycle.viewmodel, android.arch.lifecycle.livedata, android.arch.lifecycle.livedata.core,android.arch.core" />これ、方向性としてあっているのか?と少し疑問に思いながらも、とりあえずビルドができるようになりました。
ビルドを通す
とりあえず、Gradle Syncが通るようになったので、あとはビルドエラーを潰します。
Theme.AppCompatなどは使わないので、styles.xmlを削除し、AndroidManifest.xmlのapplicationに記載されたandroid:themeも削除
AndroidManifest.xml<application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/AppTheme" /> << これを削除これでビルドが通りました!
実装
実装はOSバージョンごとに処理を分岐しながらコードを記述する必要があります。
コード中で処理を分岐する場合は、Build.VERSION.SDK_INT を使用して処理を分岐します。
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { // // Android6.0(APIレベル23)以降の場合は、実行時にパーミッションのチェックを行う if (checkPermission()){ startApplication(); } } else { // // 上記以外はそのままアプリケーション開始 startApplication(); }指定したOSバージョン以下では使わないことが確実なメソッドの場合は、@TargetApiアノテーションを使うことで、Lint(静的解析ツール)のエラーを制御できます。
ただし、この方法は単にLintのエラーを出さなくしているだけなので、実行時に対象OSレベル以下の場合に、このメソッドがコールされないように自分で注意して実装する必要があります。
@TargetApi(Build.VERSION_CODES.M) private boolean checkPermission(){ int permissionCheck = ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA); if (permissionCheck != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions( this, new String[] { android.Manifest.permission.CAMERA }, PERMISSIONS_REQUEST_CAMERA); return false; } return true; }おわりに
この方法で何とか、Android2.3以降の対応アプリを開発できましたが、本来、こんなことをしないで良いようにSDKのバージョンアップをしてほしいところです。。
いまいちAndroidが好きになれなかったのですが、今回の件で、ますます嫌いになってしまった気がします。。。
- 投稿日:2019-06-25T09:27:19+09:00
2019/06/20 CICDTestNight #4 行ってきた
=====
2019/06/20(木)
19:00 〜 22:00
@渋谷ヒカリエ21F DeNAhttps://testnight.connpass.com/event/130465/
ハッシュタグ #cicd_test_night
地味なテーマ(司会者談)ながら、回を重ねるごとに参加者数が伸びている、らしい。私は初参加。
先に所感
- 『パイプラインファースト』広めたい
- 『至高のCI/CDパイプラインを実現する5つの約束』は皆様読んで欲しい
- モバイルアプリ界隈の話題が多かった、勉強にはなった
- Bitrise とかMacOSビルドとか
- SETやSREの人ばかりが登壇
- 総人口は多くないにせよ、アピールしてくるなぁ(いいなぁ、羨ましいなぁ)
- TEKTON ちょっと気になる
hisa9chi:「MacStadium使ってみた」
DeNA SWETグループ所属の井口恒志さん
CircleCI Japan Usesr Group コミュニティリーダーhttps://speakerdeck.com/hisa9chi/macstadiumshi-tutemita
- 課題
- 物理リソース
- 物理マシンの設置スペース
- 電源容量問題
- 調達速度
- 開発者の手元に届くまで約1ヶ月待つこともあり
- ハイスペック・カスタム時
- 管理コスト
- OS/SWのアップデート
- 定期的な停電対応
- そこで
- オンプレの物理マシンではなく、クラウドサービスの活用
- MacStadium 使えるのでは?
- MacStadium
- macOS物理マシンのホスティングサービス
- 物理マシンお払い出し
- 特徴
- 他の類似サービスに比べ、ハイスペックマシンもOK!
- マシン管理はWebUIのみ
- サポートは24x7
- 料金体系
- 1マシンあたり1ヶ月定額(前払い)
- 1台単位での月定額制
- 追加したマシンを返却→追加すると2台分かかる
- データセンター
- 場所による料金の変動なし
- Add-Ons
- 追加IPやファイアウォールも可能
- サポート
- 電話
- Live Chat
- 問い合わせチケット
- 基本操作
- マシンの払い出し
- ログイン:パスワードは払い出し時に生成されるチケットに記載される
- WebUI上から、Macの起動停止ができる
- 利用料金の確認
- ユーザの追加・ロールの設定
- Jenkins Slave環境構築CI
- SWETでは、社内的にJenkinsのつらみを軽減する施策を実施中
- MacStudioでJenkinsスレーブ用環境構築用のマシンを調達
- アクセス制御
- plistを使った(ファイアウォールのお金を今回はケチった)
- アクセスはVNC or SSH
- OpenVPNでMacStudioからGHEにアクセス
- AWS上にVPNサーバを用意、ふむふむ
- 動作速度は(ハイスペックなので)快適だった
- まとめ
- 試してみただけ、なので実運用に耐えるかは引き続き評価
Macのホスティングサービスというのも出てきているんですね。今回はMacOS系のビルドサーバ用途ということでしたが、弊社でもニーズがあるのでしょうか?
社外からのGHEアクセスは事例は興味深いものがありました。セキュアに、そういった利便性を確保していきたいですね。manabusakai:「CI/CD パイプラインを最速で組み立てるための 4 つのポイント」
https://speakerdeck.com/manabusakai/four-points-to-assemble-the-ci-cd-pipeline-fastest
- freee のSREの人
- 資料
- TBD
- 昨年のリリース数 278件(ほぼ毎営業日)
- 「パイプラインファースト」をやっているか?
- まずはアプリを作り始める前に、パイプラインを組み立てる
- Web TBD
- 今回はCircleCI前提
- ポイント1:いきなりCIサービス上で試そうとしない
- ローカル環境の circleci コマンドで動作確認/デバッグできる
- 詳細はQiitaに記事がある
- ポイント2:依存関係のインストールは事前に済ませる
- パイプラインの中で依存関係のインストールをしない
- キャッシュなど、設定ファイルが複雑化する
- ビルドの依存関係をすべてインストール済みのDockerイメージを用意して pull するだけ
- ポイント3:車輪の再発明やコピペを避ける
- 既存の設定ファイルのコピペが散乱
- CircleCI Orbsを活用する(共通機能がパッケージとして提供される)
- ポイント4:CIとCDを適切に分離する
- CIの延長線上でCDパイプラインを構築しがち、複雑化
- 迅速にRevertするのが難しい
- CIサービスに強力な権限を付与しないといけない
- CIからのWebhookをトリガーにCDをキックする
- Kubernetes環境なら GitOps
- まとめ
- (ポイントの列挙)
『パイプラインファースト』という言葉があるんですね。言わんとしているところは激しく同意です。クリーンビルド&デプロイができる環境が整ってからアプリ開発を始めようぜ、と。
プレゼン内で紹介されている 至高のCI/CDパイプラインを実現する5つの約束 も、ぜひ社内に広めていきたい考え。TaiheiMishima:「Voicy iOSのCI環境をゼロから整えた話」
https://speakerdeck.com/tihimsm/iosfalsecihuan-jing-wozerokaragou-zhu
- Voicy「音声xテクノロジー」の会社
- CI環境を整備した経緯
- iOSアプリの開発をしていたが、CI環境がなかった...
- 壮絶な手作業によるリリース
- 新規開発が迫ってくるヤバイ
- CI環境を作るぞ
- やったこと
- 順番めちゃくちゃ、1.5ヶ月くらいかかった(CI素人だったので)
- まずはここから整えよう
- テスト配信/プロダクション配信の自動化
- PM/QAに対して、開発者ローカルで動作確認をする必要があったので優先
- 簡単に使える
Bitrise
を使った- ビルドコンフィグ系
- 証明書まわり
- iOSの設定まわりは面倒くさい
- 証明書の更新とかとか
- 静的解析
- SwiftLint / Danger
- まとめ
- ↓の順番でやるべき
- ミスをしちゃだめなところ
- 工数が大きいところ
- コミュニケーションコストがかかるところ
この後のプレゼンでも度々出てくる Bitrise - Mobile Continuous Integration and Delivery は全然知りませんでしたが、モバイルアプリ向けのCI/CDツールとして定評があるみたい。
モバイルアプリ界隈の知見はほとんど持っていないので、ふむふむ、な内容が大半。ikesyo:「Travis CIのBuild Matrixを活用して、Swift製ライブラリをLinux対応させる」
- はてな の人
- Swiftコミッター
- Swift のライブラリ作成
- Swift Package Manager(SwiftPM)
- Server-Side Swift
- Swift on Linux
- Swiftは実はLinuxで動く
- 普通のSwiftプログラマーは macOS + Xcodeで開発してる
- ローカル開発はDocker
- swift-docker
- 複数のSwiftバージョンでテスト
- 手動ではやってられないのでCI
- Travis CI
- Build Matrix
- 2x2x2 の8通りのOSバージョン/Swiftバージョン/環境でテストする、みたいな
- ビルド環境にLinuxだけじゃなくmacOSも使える
- バージョンマネージャ(swiftenv)
- Travis CIのジョブでDockerを使うのは結構面倒だから使ってる
- Bitrise
- CircleCI
- Azule Pipeline
- でも使えるよ
SwiftはLinux上でも動くんだ、ふ〜ん。Travis CIのBuild Matrix的な仕組みは、昨今どのCIサービスでも似た機能が実装されているので、多様な環境でのビルドテストが必要な場合に重宝しそう。
ShuheiKitagawa:「5分でわかった気になるTEKTON」
- freeeの人、SRE
- CD.FOUNDATION
- にホストされることになったプロジェクト TEKTON
- TEKTON
- Knativeのサブプロジェクト、Knative Build pipeline
- Tekton Pipeline
- k8sのネイティブリソースを用いたCI/CDコンポーネント
- Pipelineを既存のPipelineの実行エンジンとして使える
- パイプライン実行部分の標準化(CIサービスごとの際を無くしたい)
- 現在
- v0.4
- 2019年中には v1.0
- 今はまだ機能が不足しており、実務で使うには時期尚早
- 今なら開発者として参加するのがおすすめ
資料が公開されていませんが、気になったセッション。まだ実用化に足る段階ではないものの、CI/CDパイプラインのエンジン部分を標準化しちゃおうぜ、というもの。これが実現できると、どのCI/CDサービスを使っていても同じ定義で実現できるようになるので、利用者としては嬉しい一方で、サービスの差別化も難しくなってきそうだな、と。
Yuki Tamazawa:「ソフトウェア品質を支えるE2Eテストのビルドパイプライン作り」
https://speakerdeck.com/srea/sohutoueapin-zhi-wozhi-eru-e2etesutofalsepaipurainzuo-ri
- JapanTaxi の人, SET
- SETは3名体制(社員)
- JapanTaxiの紹介
- やっていること
- QA体制の構築
- テストプロセスの確率・運用・改善サイクル
- 組織横断・複数プロダクト
- シフトレフト
- E2Eテストの自動化
- メインシナリオが正しく動くことを常にチェック
- 実現したいこと
- ビルドは bitrise
- E2Eテストの実行環境は、社内のMac
- テストしたら結果をSlack通知
- URLからテスト結果の詳細を確認
- 行灯ラボ(Blog)をよろしく
ここでも出てきた Bitrise。そして TestRail、これはテスト管理ツールだそう。以前、弊社内でCATを試して花咲かなかったことがあったようですが、Excelでテスト仕様書を書くのはよいとしても、テスト管理はWeb化リアルタイム自動化したいところなので、これも検討してみてもいいかもしれない。
ikuma_hayasaka:「老後必要資金2000万円時代に送る、Azure Pipelinesによる継続的テスト」
https://speakerdeck.com/ihysk/continuous-appium-e2e-testing-on-azure-pipelines
- SET @ Sony Interactive Entertainment
- こんな時代にCI/CDにお金を払っている場合なのか
- Privateリポジトリ運用はCIビルドにお金がかかる
- そこでAzule Pipelines
- Microsoftの優しさ
- 個人開発者が無料枠で試してみた
- 試験対象は個人開発のiOSアプリ
- pipeline は yml ファイルで書く
- 実際はGUIエディタでもできる
- テストは失敗しがち、デバッグしづらい
- テスト実行中の録画ができるAPIがある
- Azule Pipelineなら無料で結構できる!
やはりモバイルアプリ開発なみなさまはmacosでビルドしたくてBitriseなんだなーとしみじみ。Azule Pipelineを推している人は、他の登壇者でもいたので、今後ぐぐっと勢力を広げてくるかも、ですね。
mats:「expoアプリ開発におけるCI/CD」
(残念ながら登壇者が欠席)
- 投稿日:2019-06-25T02:07:53+09:00
【Android】データの保存と共有について
Androidアプリにおけるデータの保存と共有についてのまとめ。
- 内部ストレージ(アプリPrivateなfileの保存領域)
- 外部ストレージ(SDカードなどの共有されたfileの保存領域)
- SharedPreferences(アプリPrivateなdataの保存領域)
- データベース(アプリPrivateな構造を持つdataの保存領域)
他のアプリとの共有
Except for some types of files on external storage, all these options are intended for app-private data—the data is not naturally accessible to other apps.
If you want to share files with other apps, you should use the FileProvider API.他のアプリとファイルを共有したい場合は、
FileProvider
を使うIf you want to expose your app's data to other apps, you can use a ContentProvider.
他のアプリとデータを共有したい場合は、
ContentProvider
を使う内部ストレージ
- 他のアプリからはアクセスできない
- アプリ固有のデータを保存するのに適する
- Androidシステムがアプリ毎に固有のdirectoryを用意する
- アプリを削除すると内部ストレージ内のデータも削除される
- アプリを削除してもデータを残したい場合は
MediaStore
に保存する内部ストレージの保存方法
https://developer.android.com/training/data-storage/files.html#WriteInternalStorage内部キャッシュ
- 一時的なデータの保管に使う
- 内部ストレージの容量が少なくなるとAndroidシステムが削除するため、多くても1MBまでに留める
内部キャッシュの保存方法
https://developer.android.com/training/data-storage/files.html#WriteCacheFileInternal外部ストレージ
- 他のアプリと共有したいデータの保存に使う(スクショやDLしたファイル等)
- アプリを削除しても保存される
- 常にアクセス可能とは限らない。SDカードなどがないと不可。
- 事前にアクセスできるか確認する必要がある。
外部ストレージの保存方法
https://developer.android.com/training/data-storage/files.html#WriteExternalStorageSharedPreferences
- データの規模が小さくて構造的なデータではないデータを保存するのに適する
- 構造的なデータではない?→
Boolean
、Float
、Int
、Long
、String
- 取得と更新ができる
- key-value形式で記述されたxmlファイルとしてアプリ内に保存される
- SharedPreferencesのインスタンス生成時にxmlファイルの名前を任意に設定できる
- アプリを削除するとデータも削除される
- アプリ全体で共有されるSharedPreferencesとActivityに紐づくSharedPreferencesがある
SharedPreferencesの保存方法
https://developer.android.com/training/data-storage/shared-preferences.htmlアプリ内データベース
- AndroidではSQLite Databaseがサポートされている
- SQLiteで直接操作することもできるが、よりSQLiteが使いやすくなる
Room
というライブラリが用意されているRoomの使い方(Google推奨)
https://developer.android.com/training/data-storage/room/index.htmlSQLiteで直接操作する方法
https://developer.android.com/training/data-storage/sqlite.html今回は以上です。
- 投稿日:2019-06-25T00:14:55+09:00
【Flutter】firebase_admobでdisposeしても広告が消えない
概要
広告が表示される前に画面遷移をすると、disposeしても広告が消えない。
再現例
mypage.dartimport 'package:firebase_admob/firebase_admob.dart'; import 'package:flutter/material.dart'; class MyPage extends StatefullWidget { @override _MyPageState createState() => new _MyPageState(); } class _MyPageState extends State<MyPage> { final String appId = 'ca-app-pub-0000000000000000~0000000000'; final String adUnitId = 'ca-app-pub-0000000000000000/0000000000'; BannerAd _bannerAd; @override void initState() { super.initState(); FirebaseAdMob.instance.initialize(appId: appId); _bannerAd = _createBannerAd()..load()..show(); } @override void dispose() { _bannerAd?.dispose(); _bannerAd = null; super.dispose(); } BannerAd _createBannerAd() { return BannerAd( adUnitId: BannerAd.testAdUnitId, size: AdSize.smartBanner, listener: (MobileAdEvent event) { print("BannerAd event is $event"); }, ); } @override Widget build(BuildContext context) { return Scaffold( body: Text('MyPage'), ); } }対処法
initStateではloadだけ行い、
BannerAdのload完了時にshowするようにする。mypage.dart@override void initState() { super.initState(); FirebaseAdMob.instance.initialize(appId: appId); _bannerAd = _createBannerAd()..load(); } BannerAd _createBannerAd() { return BannerAd( adUnitId: BannerAd.testAdUnitId, size: AdSize.smartBanner, listener: (MobileAdEvent event) { print("BannerAd event is $event"); if (event == MobileAdEvent.loaded) { if (mounted) { _bannerAd..show(); } else { _bannerAd = null; } } }, ); }