- 投稿日:2019-09-09T19:10:45+09:00
AWS Amplify+IAM未認証ユーザでAppSyncを使う(Androidアプリ)
参考
Web版があったので、こちらを参考にさせてもらいました。
https://qiita.com/youn4101/items/9bca97083f07d1b9af12Android Studioのプロジェクトを新規作成
Basic Activityを作成し、ここに従う。
https://aws-amplify.github.io/docs/android/startamplifyの設定
amplify configure amplify init amplify add api ? Please select from one of the below mentioned services GraphQL ? Provide API name: amplifyguestaccess ? Choose an authorization type for the API API key ? Do you have an annotated GraphQL schema? No ? Do you want a guided schema creation? Yes ? What best describes your project: Single object with fields (e.g., “Todo” with ID, name, description) ? Do you want to edit the schema now? Yes Selected default editor not found in your machine. Please manually edit the file created at /Users/tatsiida/Documents/mobile/AmplifyGuestAccess/amplify/backend/api/amplifyguestaccess/schema.graphql ? Press enter to continueひとまず先述のURL( https://aws-amplify.github.io/docs/android/start
)をもとに、API Key でToDoのサンプルが動作するようにする。Cognito, IAM
ここでは、 Cognito サービスにて、
amplify_guest_access
というID プールを作成。
認証されていない ID に対してアクセスを有効にする
にチェックを入れる作成されたロールを確認し、
認証されていないロール
(Cognito_amplify_guest_accessUnauth_Role
)をIAMにて編集し、AppSyncへのアクセスを許可する。{ "Effect": "Allow", "Action": [ "appsync:GraphQL" ], "Resource": "*" }awsconfiguration.json
"CredentialsProvider": { "CognitoIdentity": { "Default": { "PoolId": "ap-northeast-1:b2bddc1a-f3bb-41fb-a93d-975f92457207", "Region": "ap-northeast-1" } } }, "AppSync": { "Default": { (中略) "AuthMode": "AWS_IAM" } }MainActivity.kt
val credentialsProvider = CognitoCachingCredentialsProvider(applicationContext, AWSConfiguration(applicationContext)) mAWSAppSyncClient = AWSAppSyncClient.builder() .context(applicationContext) .awsConfiguration( AWSConfiguration(applicationContext)) .credentialsProvider(credentialsProvider) .build() runMutation() // 任意の場所で上記が実行されると、DynamoDBのTableにItemが追加される。
- 投稿日:2019-09-09T16:38:44+09:00
複数Emulatorが起動している環境でApp Crawlerを使う
したかったこと
複数emulatorが起動している環境でApp Crawlerを用いようとしたら
Exception in thread “main” androidx.test.tools.crawler.launcher.exceptions.AdbExecutionException: More than one device/emulator found. Use ‘--device-name’ or ‘--device-serial-code’ option to pick a specific device/emulator. at androidx.test.tools.crawler.launcher.util.AdbExecutor.getDeviceSerialCode(AdbExecutor.java:155) at androidx.test.tools.crawler.launcher.util.AdbExecutor.<init>(AdbExecutor.java:26) at androidx.test.tools.crawler.launcher.CrawlLauncher.<init>(CrawlLauncher.java:88) at androidx.test.tools.crawler.launcher.CrawlLauncher.main(CrawlLauncher.java:93)とエラーが出てしまった。
何かオプションをつけろとログでは言っている割に、公式ドキュメントにはこのオプションの説明が見当たらなくて、ハマってしまったのでメモ。
解決法
--device-serial-code 動かしたい端末のserialナンバーをオプションに与える。
- エラーログでは--device-nameオプションか--device-serial-codeオプションを要求されているが、自分の環境(app crawler : Beta1.1 / PC : MacOS Mojave バージョン10.14.4)では--device-nameオプションは正常に動かなかった。 (serial numberを与えても、端末名を与えてもエラーが出た。)
コード例
# 起動しているデバイスを取得 # 1行目はのぞいて[端末名 device]となっているコマンド結果から端末名だけ取り出して配列に入れる DEVICES=$(adb devices | sed “1d;s/[[:space:]].*$//“) echo $DEVICES DATE=date +%y-%m-%d_%H-%M-%S # 1台ずつapkをインストールしてテストを行う for device in $DEVICES do #app-crawlerのapp-crawlerオプションはちゃんと動かなかったのでインストールは個別に行う adb -s $device install ./app/build/outputs/apk/debug/hogehoge-debug.apk # 結果を収納するフォルダを作成して移動 mkdir -p AppCrawler_${device}/${DATE} cd ./AppCrawler_${device}/${DATE} java -jar ../../app-crawler/crawl_launcher.jar --android-sdk $ANDROID_HOME --device-serial-code $device --app-package-name com.kkkkan.hogehoge doneその他・感想
--apk-fileオプションも、実機ならちゃんと動いたけどemulatorだと全然動かなくて先に adb -s installでapkをインストールしてから--app-package-nameオプションで別途app crawlerを起動しなきゃいけなかったし、betaというだけあってまだまだバグだらけっぽい。
参考にしたもの
https://qiita.com/kafumi/items/fff5324a4bb70b7b7986
https://qiita.com/muran001/items/ffd56f8a7dd76e3968fb
- 投稿日:2019-09-09T16:35:42+09:00
[Kotlin用DIフレームワーク] Koinの使い方
Koinとは
タイトルにモロに書いてありますが、
A pragmatic lightweight dependency injection framework for Kotlin developers.
Kotlin開発者向けの実用的な軽量のDI(依存性注入)フレームワークです。
Daggerと比べてわかりやすいと感じたので、備忘録のために使い方をまとめます。使い方(3ステップ)
1. Moduleを宣言する
// いくつかのクラスがあるとします。 class Controller(val service : BusinessService) class BusinessService() class MyViewModel(context: Application) : AndroidViewModel(context) // DSLでそれらを指定してモジュール宣言をするだけです。 val myModule = module { factory { Controller(get()) } single { BusinessService() } viewModel { MyViewModel(androidContext() as Application) } }
module
やfactory
などの書き方はDSLですので、詳しくはこちらを参照してください。コンストラクタがある場合は、
get()
を使用します。
注入したいクラスをSingletonにしたい場合はsingle
を使用します。また、ViewModelの宣言はDSLで
viewModel
と指定できます。このとき、Activity-Fragment間でViewModelを共有したいときに用いるAndroidViewModel
を継承しているViewModelの場合はコンストラクタにApplicationを持っているので、get()
でもいいですが、androidContext() as Application
でも取得できるらしいので、こちらを使っておきます。モジュールを宣言する場所は、例えば、
di
などのパッケージを切って、その中にmyModule
などのファイルを作成してその中でトップレベルプロパティとして宣言するのが良いかと。2. Koinを開始する
class MyApplication : Application() { override fun onCreate(){ super.onCreate() // `startKoin` でKoinを開始します。 startKoin { // Contextを宣言し、 androidContext(this@MyApplication) // 先ほど宣言したmoduleを指定します。 modules(myModule) } } }Applicationクラスを継承したクラス内で色々と宣言して
startKoin
するだけです。3. インスタンスを注入する
// あとは、Activity内でインスタンスを注入するだけです。 class MyActivity() : AppCompatActivity() { // serviceプロパティの中へBusinessServiceを遅延注入する。 val service : BusinessService by inject() val viewModel: MyViewModel by viewModel() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // もしくは、直接インスタンスを取得する。 val service : BusinessService = get() } }最後は、
by inject()
でインスタンスを注入するだけです。
そのプロパティが初めて呼ばれたときに注入しています。
もしくは、get()
でも取得可能なのですね。(知らなかった)viewModelを注入したいときは、
by viewModel()
を使用します。
DI前だったら
lateinit var vm: MyVeiwModel
からの
vm = ViewModelProviders.of(this).get(MyViewModel::class.java)
とかわんさかコード書かないといけなかったから、これがなくなるのは助かる。Activity, Fragment, Service以外のクラスで注入したい場合は、
KoinComponent
インターフェースを実装すれば、注入できるようになります(by inject()
が使用できるようになります)終わりに
Koin万歳!!!
Daggerも慣れてしまえば問題ないですが、Koinより学習コストが高めなので(私の理解力の問題かも...)、Daggerを使用しなければならない場合以外は、Koin使いたいなぁ。※追記
と思っていましたが、シニアエンジニアの方にKoinは動作が遅いということをお聞きし、調べてみました。次のサイトで様々なDIフレームワークでのパフォーマンスを調べてくれてました。
https://github.com/Sloy/android-dependency-injection-performance
Dagger2めちゃくちゃ早くて驚いた。。。
これは学習コスト云々以前に、一人のエンジニアとして品質の面でDaggerを使うべきだなと感じさせられました。参考(というか公式)
- 投稿日:2019-09-09T15:14:45+09:00
WebRTCとWebViewの組み合わせについて
WebRTCとWebViewの組み合わせについて
Android/iOS共にWebViewでWebRTCの映像が受信できるか検証してみました。
WebRTCの配信基盤にはagora.ioを利用します。前提
今回の検証についてはWebView側からの映像送信は試しません。
getUserMedia()あたりが正常に動作しないとの記事をいくつか見かけている為です。
(もしかしたら動いているかもしれませんが)
agora.ioでの動作状況については以下にドキュメントがあります。
https://docs.agora.io/en/faqs/web_on_mobile環境
配信側ブラウザ:Chrome76.0.3809.132
視聴側デバイス:
・Huawei P20 lite/Android9.0
・Xperia Z5 Premium/Android5.1
・iPhone7/iOS12.4.1
SDK:agora.io WebSDK2.8.0WebRTCの実装
配信側、受信側共にagora.ioのWebSDKを利用しています。
サンプルコードはこちら
host.html:配信側
audience.html:受信側(WebView側で指定するページ)ポイントとしてはコーデックをVP8に指定しているところです。
廉価版のHUAWEI端末ではH264のデコードに対応していないもの(P20 lite等)がある為です。
尚、iOSのSafariは12.1からVP8対応しております。host.htmlclient = AgoraRTC.createClient({mode: 'live',codec:'vp8'});Androidの実装
Androidについての実装については特に難しい点はありませんでした。
Webアプリケーション側の修正をすぐに反映させる為にsetCacheMode(WebSettings.LOAD_NO_CACHE);にてキャッシュを読み込まないようにする程度かと思います。
サンプルコードはこちらMainActivity.java@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); WebView myWebView = (WebView)findViewById(R.id.webView1); myWebView.setWebViewClient(new WebViewClient()); myWebView.setWebChromeClient(new WebChromeClient()); myWebView.getSettings().setCacheMode(WebSettings.LOAD_NO_CACHE); myWebView.loadUrl("URL");//URLはaudience.htmlを指定 myWebView.getSettings().setJavaScriptEnabled(true); }結果画面
特に問題なく動作しています。
iOSの実装
サンプルコードはこちら
一番最初はWKWebViewを利用して試していました。
「iOS」「WebView」で検索するとよくヒットします。
結果としてはこのWKWebViewでは映像の視聴はできませんでした。
(やる方法はあるのかもしれませんが。。。)ViewController.swiftlet config = WKWebViewConfiguration() config.allowsInlineMediaPlayback = true let webView = WKWebView(frame: self.view.frame, configuration: config)一応、javascriptは動いているようですが、映像が映りません。またalert()等も動作せず、少しデバッグがしにくい状況でした。
そこで、SFSafariViewControllerというものを見つけたのでこれで動作確認をしてみました。ViewController.swiftoverride func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) let url = URL(string:"URL") if let url = url{ let vc = SFSafariViewController(url: url) //URLはaudience.htmlを指定 present(vc, animated: true, completion: nil) } }このコードで無事に動作しました。
結果画面
「SFSafariViewControllerはiOS9.0以降使用可能でSafariの標準機能を備えたViewControllerを作成できます。」とのこと。
最後に
実際にプロダクトとして利用するには他の問題があるかもしれませんが、ひとまずWebViewでの映像再生は可能ということが分かりました。
agora.ioに関するお問い合わせはこちらから
- 投稿日:2019-09-09T15:10:33+09:00
Android WebView から別アプリを開く
AndroidのWebViewでは
tel
やmailto
などのリンクを踏んでも電話アプリやメールアプリが起動しないためそれに対応するための実装が必要です。
また暗黙的Intentに対応する場合も、そのための実装が必要です。
これらの実装方法を紹介します。URL判定処理
URLからIntentを作成します。
class WebViewRequestValidation { fun isTransitionToOtherApp(url: Uri): TransitionToOtherApp { return when (url.scheme) { "tel" -> TransitionToOtherApp.Yes(Intent(Intent.ACTION_DIAL, url)) "mailto" -> TransitionToOtherApp.Yes(Intent(Intent.ACTION_SENDTO, url)) "intent" -> TransitionToOtherApp.Yes(Intent.parseUri(url.toString(), Intent.URI_INTENT_SCHEME)) else -> TransitionToOtherApp.No() } } sealed class TransitionToOtherApp { class Yes(val intent: Intent):TransitionToOtherApp() class No():TransitionToOtherApp() } }Intent起動処理
上記で作成したIntentを起動します。処理の流れは以下の通りです。
- 対象Activityが存在すれば、startActivityを実行する。
- 存在しないActivityをstartActivityしてしまうと、アプリがクラッシュします。
- intentから
browser_fallback_url
が取得できれば、そのURLを表示する。
- こちらの処理について、詳しくはChromeのドキュメントを参照ください。
- GooglePlayStoreアプリがアクティブであれば(存在すれば)、GooglePlayStoreアプリを起動する。
- 上記全てがヒットしない場合、
https://play.google.com/store/apps/details?id=$applicationId
を表示する。最後の
https://play.google.com/store/apps/details?id=$applicationId
は、表示してもアプリをダウンロードできないので、なにかユーザにメッセージを表示するほうが適切かもしれません。class ImplicitIntentStarter(private val wContext: WeakReference<Context>) { fun startActivity(intent: Intent) { val context = wContext.getNullable() ?: return val packageManager = context.packageManager if (intent.resolveActivity(packageManager) != null) { context.startActivity(intent) return } val fallbackUrl = intent.getStringExtra("browser_fallback_url") if (fallbackUrl != null) { // ブラウザ起動など return } startGooglePlayStoreActivity(intent.`package`) } fun startGooglePlayStoreActivity(applicationId: String) { val context = wContext.getNullable() ?: return val packageManager = context.packageManager val marketIntent = Intent(Intent.ACTION_VIEW).setData(Uri.parse("market://details?id=$applicationId")) if (marketIntent.resolveActivity(packageManager) != null) { context.startActivity(marketIntent) return } context.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse("https://play.google.com/store/apps/details?id=$applicationId"))) } } // WeakReferenceから取得する値を null safe に扱うための拡張です。 fun <T> WeakReference<T>.getNullable(): T? = get()WebViewのリクエストをインターセプト
最後に、WebViewのリクエストをインターセプトし、上記の処理を実装します。
private val validation = WebViewRequestValidation() private val implicitIntentStarter = ImplicitIntentStarter(wContext) val webViewClient = object : WebViewClient() { override fun shouldOverrideUrlLoading(view: WebView?, url: String?): Boolean { // WebViewの画面遷移をさせたくない場合は true を返す。 val allowedRequest = false val notAllowedRequest = true val urlString = url ?: return notAllowedRequest val url = Uri.parse(urlString) validation.isTransitionToOtherApp(url).let { when (it) { is WebViewRequestValidation.TransitionToOtherApp.Yes -> { implicitIntentStarter.startActivity(it.intent) return notAllowedRequest } is WebViewRequestValidation.TransitionToOtherApp.No -> {} } } return allowedRequest } } webView.setWebViewClient(webViewClient)
- 投稿日:2019-09-09T11:40:38+09:00
Raspberry Pi 3B+でAndroidOSを起動させる時の注意点
Raspberry Pi 3B+でAndroidOSを起動させる時の注意点
ラズパイでAndroidOSを起動する記事や情報が多く流れています。
どの記事を読んでも公開されているOSイメージをコピーして起動するだけというものでした。
そしてすんなりと起動できています。これは私もRaspberry Pi 3B+を購入したのでAndroidOSが動作するか確認していた時のお話です。
USBブートの罠
「Raspberry Pi 3B+はUSBブートが標準で対応」というメリットが今回非常に足をひっぱりました。
最初に購入したラズパイが3B+なので、OSの起動はUSBスロットからという固定観念をもっていました。これが迷宮への誘いでした。。。
試したAndroidOSの数々
・LineageOS
・emteria.OS
・Android Things
・その他野良のようなOS
これらのOSイメージをSDに焼いて、毎回USBブートしていましたが、、、
・そもそも画面に何も映らない
・レインボー画面から遷移しない
・何かしらファイルを探しているプロンプトだけが出る
という状況でした。困った時は
こんな状況でほぼ諦めかけていましたが、他の人に状況だけ雑談しているときにあるページを見つけました。
そこには、「オペレーティングシステムの読み込みおよびデータ保存用のMicro SDポート」なるものが3B+にあるとのこと。
声に出して誰かに伝えると自分で突然解決法が見つかる法則が発動しました。そもそも
Raspberry Pi 3B以前から利用している方からすれば当たり前のポートかもしれません。
私の運が悪かったのは「Raspberry Pi 3B+はUSBブートが標準で対応」=USBブートのみ という概念を持ってしまったからです。ということで、、、
Micro SDポートに素直にカードを挿して、LineageOSは無事に動作しました。あといくつかの野良OSも起動しました。
(Android Thingsはまだ未対応のようです)やりたい事
AndroidOSが無事に稼働したので、agora.io SDKを利用した映像配信システムを構築しようと検討中です。
agora.ioに関するお問い合わせはこちらから