20200630のAndroidに関する記事は5件です。

Flutter(Dart)でValidationを実装する

バリデーション

validation_helper.dart
// 英数のみ、6桁のバリデーション

class Validator {
  Validator._();

  static bool isValidHoge(String hoge) {
    const _hogeRegExpString = r'^[0-9a-zA-Z]{6}$';
    return RegExp(_hogeRegExpString, caseSensitive: true).hasMatch(hoge);
  }
}

接頭辞rを付することでraw(生)文字列として扱うことができます。(エスケープシーケンス回避のため)

参考記事
1. form用正規表現判定/備忘
2. RegExp constructor

実装例

example_notifier.dart
// freezedを使用した実装例

class Hoge {
  void onChangeHoge(String hoge) {
    state = state.copyWith(hoge: hoge);
    if (Validator.isValidHoge(hoge)) {
      state = state.copyWith(isEnable: true);
    } else {
      state = state.copyWith(isEnable: false);
    }
  }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

agora.io RENDERモード (映像表示モード)

概要

agora.io の VideoSDK for Native では、RENDERモードという機能により、画面上の映像表示設定をすることができます。
本記事では、Androidを利用して、本機能の利用方法および各モードの映像表示を説明します。

RENDERモードの利用方法

RENDERモードは、以下のAPIコール時に引数で指定します。

setupLocalVideo と setupRemoteVideo は、ビデオ通話開始時にそれぞれ自拠点、他拠点の映像表示を初期化するためのAPIです。
setLocalRenderMode と setRemoteRenderMode は、初期化後に映像表示を変更するためのAPIです。

// Java
private void setupLocalVideo() {

    // Enable the video module.
    mRtcEngine.enableVideo();

    // Create a SurfaceView object.
    private FrameLayout mLocalContainer;
    private SurfaceView mLocalView;

    mLocalView = RtcEngine.CreateRendererView(getBaseContext());
    mLocalView.setZOrderMediaOverlay(true);
    mLocalContainer.addView(mLocalView);
    // Set the local video view.
    VideoCanvas localVideoCanvas = new VideoCanvas(mLocalView, VideoCanvas.RENDER_MODE_HIDDEN, 0);
    mRtcEngine.setupLocalVideo(localVideoCanvas);
}

各RENDERモードについて

RENDERモードは、2020年6月現在3種類あります。
各モードについて説明します。
※2拠点入室時のほうが各モードの差異がわかりやすいので、1拠点入室時と2拠点入室時両方の画面キャプチャを載せています。

HIDDEN

余白なく、映像を表示します。
カメラ映像と画面のアスペクト比が一致しない場合、カメラ映像の一部をトリミングして、表示します。

HIDDEN1.jpg   HIDDEN2.jpg

FIT

カメラで取得したアスペクト比のまま、映像を画面上に表示します。
カメラ映像と画面のアスペクト比が一致しない場合、画面上に余白 (黒い部分) ができます。

FIT1.jpg   FIT2.jpg

FILL

余白なく、映像を表示します。
カメラ映像と画面のアスペクト比が一致しない場合、映像を拡大もしくは収縮させて、画面全体に映像が表示されるようにします。

FILL1.jpg   FILL2.jpg

最後に

agora.ioに関するお問い合わせはこちらから
スクリーンショット 0001-08-15 13.41.56.png

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

【Kotlin】初心者向け: 画面回転やスリープ後のUIの保持

はじめに

 今回は画面を回転させたときや本体をスリープさせた後、ビューの一部がその前後で変わってしまう問題について取り組みます。下の簡易的なボタンカウンターアプリでその様子が確認できます。縦画面でカウントアップさせたとき、横に切り替えると数字が0に戻ってしまうのです。
キャプチャ.PNG
キャプチャ2.PNG

原因

 この問題の原因は、画面の向きを変えるなどの諸動作によって、Activityがライフサイクルを一からやり直す事にあります。これによってハードウェアは、柔軟にかつ素早くユーザーのアクションを反映させることが出来ますが、同時にUIまでもが破棄されて初期化されてしまいます。これを防ぐためには、ライフサイクルが終わる前に、UI等の必要な情報を一時的に保持しておく必要があります。

*ライフサイクルについてはこちら:https://qiita.com/K4N4/items/2f4babe2bab67ddacf89

onSaveInstanceState

一時的に情報を保持する方法の一つに、onSaveInstanceStateを利用するというものがあります。この方法はあまり大容量ではない情報を、簡単に保持する時に利用します。使い方は非常にシンプルです。

override fun onSaveInstanceState(outState: Bundle) {
        super.onSaveInstanceState(outState)

        outState?.putInt("key", i)
    }

    override fun onRestoreInstanceState(savedInstanceState: Bundle) {
        super.onRestoreInstanceState(savedInstanceState)

        i = savedInstanceState?.getInt("key", 0)
        textView.text = i.toString()
    }

 onSaveInstanceStateメソッド(データを保持する)とonRestoreInstanceStateメソッド(保持したデータを復元する)を呼び出し、put〇〇get〇〇で実際にデータの受け渡しを行っています。
 onSaveInstanceStateメソッドのoutState?.putInt("key", i)というのはoutState(データをメモリに保存する実体)に"key"(任意)というキーを使って、iというIntの値を保持する。という事を指しています。キーとはその名の通り鍵の事です。呼び出し側と共通のキーを持たなければ、データの受け渡しは出来ません。
 onRestoreInstanceStateメソッドのi = savedInstanceState?.getInt("key", 0)は、Activityが破棄されて値が初期化されてしまった i に、保持したデータの実態savedInstanceStateからgetIntメソッドで共通のキーを持つ値を取り出しています。getIntの第二引数は、putIntがnullの場合の値を設定します。
 この二つのメソッドとその中身を入れるだけで、簡単に設定した値の保持が行えます。

onSaveInstanceStateとonRestoreInstanceStateとライフサイクル

 先述したようにこの二つのメソッドを使えば、簡単にUIの保持を行えます。では、この二つのメソッドはActivityのライフサイクルにおいて、どの段階で機能してるのでしょうか?
android_jitsumu2_6.jpg
それを示すのがこの図です。この図はActivityの基本的な工程に、今回用いた二つのメソッド等を加えたものです。この図の通り、onSaveInstanceStateはonPauseの後に値の保持を、onRestoreInstanceStateonStartの後に値の復元を行っています。onRestoreInstanceStateの値はonCreateに影響を及ぼさなかったり、onSaveInstanceStateが呼び出されたら必ずアプリが止まることなどが分かります。

実際にこのライフサイクルを可視化してみる

class MainActivity : AppCompatActivity() {

    private var i : Int = 0

    override fun onCreate(savedInstanceState: Bundle?) {
        Log.d(TAG, "onStart: called")
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        button.setOnClickListener {
            i += 1
            textView.text = i.toString()
        }
    }

    override fun onStart() {
        Log.d(TAG, "onStart: called")
        super.onStart()
    }

    override fun onResume() {
        Log.d(TAG, "onResume: called")
        super.onResume()
    }

    override fun onPause() {
        Log.d(TAG, "onPause: called")
        super.onPause()
    }

    override fun onSaveInstanceState(outState: Bundle) {
        Log.d(TAG, "onSaveInstanceState: called")
        super.onSaveInstanceState(outState)

        outState.putInt("key", i)
    }

    override fun onRestoreInstanceState(savedInstanceState: Bundle) {
        Log.d(TAG, "onRestoreInstanceState: called")
        super.onRestoreInstanceState(savedInstanceState)

        i = savedInstanceState.getInt("key", 0)
        textView.text = i.toString()
    }

    override fun onStop() {
        Log.d(TAG, "onStop: called")
        super.onStop()
    }

    override fun onRestart() {
        Log.d(TAG, "onRestart: called")
        super.onRestart()
    }

    override fun onDestroy() {
        Log.d(TAG, "onDestroy: called")
        super.onDestroy()
    }
}

MainActivityの中身をこのようにすることで、実際にどこでデータの保持等が行われているかログを通して可視化することが出来ます。このソースコードでは、上で示したカウントアップ機能のためにデータのやり取りを行っています。上手くいけば、各工程ごとにログに出力がなされます。是非回転などさせて試してみてください。

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

Ionic & golangでリアルタイム大喜利アプリを作りました

アプリのリリースまでなんとか漕ぎ着けたので、宣伝も兼ねて使ったフレームワークについて紹介します。

どんなアプリか

写真で一言アプリです。
特徴的なのはリアルタイムで集まった人たちが、お互いに投稿し合ったボケを評価し合う部分です。

マッチングした最大10名が、制限時間内に一つのお題写真に対してボケを投稿します。
投稿はお互いに評価することができ、一定数イイネを集めると一本獲得になります。

こちらからダウンロード可能です。
よかったら遊んでみてください。

android
https://play.google.com/store/apps/details?id=jp.co.popbits.funnyapp

ios
https://apps.apple.com/jp/app/funny-one/id1515018792

アプリ側

ionic

アプリのベースはIonic Frameworkで作成しました。

https://ionicframework.com/

ざっくりいうと、
webview + Angular
にネイティブっぽいリッチな見た目のコンポーネントと、共通化されたプラグインが利用できます。
プラグインの例としてはSocial Sharingやdeeplink等があります。

Angularではなく、vueやreactで記載することも可能なようです。
※reactは最近入ったらしい。


android, ios同一のコードで記載できるので工数削減になりました。
環境構築やビルドなどもかなりスムーズにできました。
(多少のつまづきはありますが、ネイティブアプリの環境構築にはつきものです)

懸念点として、webviewベースであるためパフォーマンスがあまり良くないのではないか気になりましたが、
端末の性能も上がってきているからか、思ったよりサクサク動いてくれています。

createjs

大喜利バトルの部分はcreatejsで作成しました。
https://createjs.com/

Adobe Animate
https://www.adobe.com/jp/products/animate.html
でcreatejs用にアニメーションを書き出しています。

アプリへのつなぎ込みには色々と試行錯誤が必要でしたが、細かい話になるので割愛します。

大喜利バトルでは後述するgolangサーバとwebsocketで接続し、イベント毎にアニメーションを変化させます。


サーバ側

golang

リアルタイムや並列処理に強そうなので採用しました。
pythonもよく書くのですが、golangは型があるのでバグが起きづらい、
パフォーマンスが良いなどのメリットがあります。

コードは誰が書いても同じ書き方になるよう矯正され、必然的に可読性が高くなります。

一方、リアルタイム部分(websoket接続)はgoroutine と channelを駆使して作るのですが、
正直自分以外理解できないコードになってしまったと思います。

並列処理そのものが難しいのもあると思います。
通常の処理とは違いコードの上から追っていくだけでは読み解くことができず、
どこからchannnelにアクセスが有るなど把握していないといけません。

並列処理は色々な書き方ができるので正解がない感じです。

gin

https://github.com/gin-gonic/gin

デファクトスタンダードっぽいので採用しました。
良さげです。

melody

https://github.com/olahol/melody

websocketを扱う際にいい感じの機能を提供してくれます。

sqlx + squirrel

https://github.com/jmoiron/sqlx
クエリ結果をstructsにマッピングしてくれます。

https://github.com/Masterminds/squirrel
SQLクエリビルダーです。
where句の検索条件をqueryオブジェクトに対して注入していく、
みたいなことがやりたい場合はこれでSQLを作ります。

最初はgormを使っていたのですが、最終的に上の構成に書き換えることにしました。
gormは人気があるようなので使っていたのですが、挙動が直感的でなかったのと、
思いも寄らない部分で大量のSQLを発行されていたことがあったりで、嫌になってやめました。

まとめ

色々紹介しましたが、ionicはかなり気軽にアプリが作成できるので本当におすすめです。

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

【Android】EditTextで日本語入力⇔英語入力の相互切替ハンドリングは難しい

初めに

すみません、解決策を提示する内容ではないです。

Emailアドレスの入力時は
inputType:textEmailAddress
で英字入力モード
テキスト入力時はを初期表示にしたい
inputType:text
で日本語入力モードを初期表示にしたい
と格闘し、結論無理と行き着きました。。

非常に時間を浪費し、悲しみを覚えたので、同じような悩みを抱えて嵌り続ける方がもしいたら早めに切り上げるよう共有したくメモします。
いやいや出来るよ!という賢者がいらっしゃったら是非ご教示下さい。。

事象

以下のように、
TextInputEditTextにを指定して初期のソフトキーボードを英字入力にしようとしたのですが、
別のEditText等で日本語入力に切り替わると、以降inputType:textEmailAddressの初期表示も日本語入力になってしまいます。

activity_main.xml
<com.google.android.material.textfield.TextInputLayout
  android:id="@+id/login_input_email_layer"
  android:layout_width="335dp"
  android:layout_height="wrap_content"
  android:layout_marginTop="15dp"
  android:minHeight="60dp"
  app:layout_constraintEnd_toEndOf="parent"
  app:layout_constraintHorizontal_bias="0.5"
  app:layout_constraintStart_toStartOf="parent"
  app:layout_constraintTop_toBottomOf="@+id/login_error_message_layer">

      <com.google.android.material.textfield.TextInputEditText
        android:id="@+id/login_input_email"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:ems="10"
        android:hint="@string/login_email_placeholder"
        android:inputType="textEmailAddress" // 何故か効かない。。。
        android:textColor="@color/font_color_black"
        android:textSize="22sp"
        android:maxLength="50"
        android:maxLines="1" />
</com.google.android.material.textfield.TextInputLayout>

image.png

最後に

内容がなく恐縮です。。
InputFilterを使用してせめてメールアドレス形式のみ入力可能にしようかと思いましたが、
ユーザビリティがかなり下がったので見送りました。
いつか日本語も正しくハンドリング出来るようになると嬉しいですね。。
海外企業なので厳しいでしょうけど。。。

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