- 投稿日:2020-04-09T18:29:58+09:00
眺めて覚える C# Xamarin Hello World(1)
iPhoneの電池が持たなくなった。携帯電話の寿命は、大体3年ぐらいだ。
はたして5万円以上の値打ちがあるのかが疑問である。
そこでAndroidにすることにした。
さくさく動くし、電池も3日もつ!
値段も5万円から2万円適正価格だ!
前から気になっていたXamarinを使ってC#でプログラムしてみた。
1.Visual studio 2019のインストールについては、色々な記事があるので参考にしてほしい。
私の買ったディバイスは、UMIDIGI Xだ。まず、メーカサイトからディバイスドライバーをダウンロードしてWindows 10にinstall しよう。
開発者モードにするには、「設定」アプリ内で「ビルド番号」を7回連続してタップ
Visual Studio 2019でプロジェクトを作成する。
Xamarin Formsを選択する。
Windows APPを作成するような感覚で開発できる。
空白のプロジェクトを作成する。
USBにより接続されたAndroidが表示されているか、確認する。
とりあえず下記のように表示されればOK
プログラムの内容を見てみよう
MainPage.xamlをダブルクリックするとソースが現れる。
XAMLはXMLをベースとしたマークアップ言語です。UIを定義するのに用いられます。MainPage.xaml<?xml version="1.0" encoding="utf-8" ?> <ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:d="http://xamarin.com/schemas/2014/forms/design" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" x:Class="HelloWorld.MainPage"> <StackLayout> <!-- Place new controls here --> <Label Text="Welcome to Xamarin.Forms!" HorizontalOptions="Center" VerticalOptions="CenterAndExpand" /> </StackLayout> </ContentPage>MainPage.xaml.csusing System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Text; using System.Threading.Tasks; using Xamarin.Forms; namespace HelloWorld { // Learn more about making custom code visible in the Xamarin.Forms previewer // by visiting https://aka.ms/xamarinforms-previewer [DesignTimeVisible(false)] public partial class MainPage : ContentPage { public MainPage() { InitializeComponent(); var layout = new StackLayout(); var lb = new Label() { Text = "Hello World",FontSize=40 }; layout.Children.Add(lb); this.Content = layout; } } }Xamlでうにゃうにゃ書いていたレアウトをプログラムで書くと上記のようになる。
多分、javaで書くよりすっきりしていて良い。
- 投稿日:2020-04-09T18:29:58+09:00
眺めて覚えるXamarin Hello World
iPhoneの電池が持たなくなった。携帯電話の寿命は、大体3年ぐらいだ。
はたして5万円以上の値打ちがあるのかが疑問である。
そこでAndroidにすることにした。
さくさく動くし、電池も3日もつ!
値段も5万円から2万円適正価格だ!
前から気になっていたXamarinを使ってC#でプログラムしてみた。
1.Visual studio 2019のインストールについては、色々な記事があるので参考にしてほしい。
私の買ったディバイスは、UMIDIGI Xだ。まず、メーカサイトからディバイスドライバーをダウンロードしてWindows 10にinstall しよう。
開発者モードにするには、「設定」アプリ内で「ビルド番号」を7回連続してタップ
Visual Studio 2019でプロジェクトを作成する。
Xamarin Formsを選択する。
Windows APPを作成するような感覚で開発できる。
空白のプロジェクトを作成する。
USBにより接続されたAndroidが表示されているか、確認する。
とりあえず下記のように表示されればOK
プログラムの内容を見てみよう
MainPage.xamlをダブルクリックするとソースが現れる。
XAMLはXMLをベースとしたマークアップ言語です。UIを定義するのに用いられます。MainPage.xaml<?xml version="1.0" encoding="utf-8" ?> <ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:d="http://xamarin.com/schemas/2014/forms/design" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" x:Class="HelloWorld.MainPage"> <StackLayout> <!-- Place new controls here --> <Label Text="Welcome to Xamarin.Forms!" HorizontalOptions="Center" VerticalOptions="CenterAndExpand" /> </StackLayout> </ContentPage>MainPage.xaml.csusing System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Text; using System.Threading.Tasks; using Xamarin.Forms; namespace HelloWorld { // Learn more about making custom code visible in the Xamarin.Forms previewer // by visiting https://aka.ms/xamarinforms-previewer [DesignTimeVisible(false)] public partial class MainPage : ContentPage { public MainPage() { InitializeComponent(); var layout = new StackLayout(); var lb = new Label() { Text = "Hello World",FontSize=40 }; layout.Children.Add(lb); this.Content = layout; } } }Xamlでうにゃうにゃ書いていたレアウトをプログラムで書くと上記のようになる。
多分、javaで書くよりすっきりしていて良い。
- 投稿日:2020-04-09T18:29:58+09:00
眺めて覚える C# Xamarin Hello World
iPhoneの電池が持たなくなった。携帯電話の寿命は、大体3年ぐらいだ。
はたして5万円以上の値打ちがあるのかが疑問である。
そこでAndroidにすることにした。
さくさく動くし、電池も3日もつ!
値段も5万円から2万円適正価格だ!
前から気になっていたXamarinを使ってC#でプログラムしてみた。
1.Visual studio 2019のインストールについては、色々な記事があるので参考にしてほしい。
私の買ったディバイスは、UMIDIGI Xだ。まず、メーカサイトからディバイスドライバーをダウンロードしてWindows 10にinstall しよう。
開発者モードにするには、「設定」アプリ内で「ビルド番号」を7回連続してタップ
Visual Studio 2019でプロジェクトを作成する。
Xamarin Formsを選択する。
Windows APPを作成するような感覚で開発できる。
空白のプロジェクトを作成する。
USBにより接続されたAndroidが表示されているか、確認する。
とりあえず下記のように表示されればOK
プログラムの内容を見てみよう
MainPage.xamlをダブルクリックするとソースが現れる。
XAMLはXMLをベースとしたマークアップ言語です。UIを定義するのに用いられます。MainPage.xaml<?xml version="1.0" encoding="utf-8" ?> <ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:d="http://xamarin.com/schemas/2014/forms/design" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" x:Class="HelloWorld.MainPage"> <StackLayout> <!-- Place new controls here --> <Label Text="Welcome to Xamarin.Forms!" HorizontalOptions="Center" VerticalOptions="CenterAndExpand" /> </StackLayout> </ContentPage>MainPage.xaml.csusing System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Text; using System.Threading.Tasks; using Xamarin.Forms; namespace HelloWorld { // Learn more about making custom code visible in the Xamarin.Forms previewer // by visiting https://aka.ms/xamarinforms-previewer [DesignTimeVisible(false)] public partial class MainPage : ContentPage { public MainPage() { InitializeComponent(); var layout = new StackLayout(); var lb = new Label() { Text = "Hello World",FontSize=40 }; layout.Children.Add(lb); this.Content = layout; } } }Xamlでうにゃうにゃ書いていたレアウトをプログラムで書くと上記のようになる。
多分、javaで書くよりすっきりしていて良い。
- 投稿日:2020-04-09T17:37:31+09:00
Fused Location ProviderでKotlin FlowとLiveDataを再入門してみた
前書き
CodeLabのBuilding a Kotlin extensions libraryをやってみました。
名前からしてライブラリの作り方のベストプラクティスじゃないかと思って、勉強しようと思ったら拡張関数の内容でした。
でも拡張関数よりも、中で使ってるFlowのほうがすごかったです。勉強したいものと違いましたが、改めてFlowの凄さを再認識できたので記事にして記録を残そうと思いました。
このCodeLabの内容は?
このCodeLabはリアルタイムで地理情報を表示するアプリを作っています。
数秒ごとにLocationを更新しています。
やろうとしていることは結構かんたんですが、結構クセがあるAPIと、Androidのライフサイクルの特有の問題があるので、気をつけて書かないと結構かんたんに罠にハマります。(ライフサイクルの難しさはこの記事であまり解説しないつもり)一応このCodeLabのタイトルは「Building a Kotlin extensions library」なので、拡張関数のすごさを説明するものでしたが、中で使ってるFlowがすごすぎて、拡張関数なんて目じゃないレベルでした。
1. Flowを使わないJavaのAPI
地理情報を表示するのにLocationServicesを使います。このAPIはJavaで書かれているので、Callbackを前提とした使い方になっています。
使い方としてがあります。
「リアルタイムのLocation情報を取得する」でも十分ですが、最初の更新まで時間がかかるのと、回線不安定などでデータを上手く取得できない場合もあるので、だいたい「前回取得した最後のLocationを取得する」と組み合わせて使うイメージです。
前回取得した最後のLocationを取得する
val fusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(this) // Activity or Context fusedLocationProviderClient.lastLocation.addOnSuccessListener { // 成功時の処理 ... }.addOnFailureListener { // 失敗時の処理 // 位置情報の権限がない場合はここに来る ... }なぜか
lastLocation
の戻り値はTask<Location?>
なので、結果をaddOnSuccessListener
とaddOnFailureListener
で非同期で取得しなければいけない。リアルタイムのLocation情報を取得する
val callback = object: LocationCallback() { override fun onLocationResult(result: LocationResult?) { // コールバックで画面等を更新する } } // Locationを取得する頻度を設定する val locationRequest = LocationRequest().apply { interval = 3000 fastestInterval = 2000 priority = LocationRequest.PRIORITY_HIGH_ACCURACY } val fusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(this) // Activity or Context // 上記の設定をセットアップして、更新を開始する fusedLocationProviderClient.requestLocationUpdates( locationRequest, callback, Looper.getMainLooper() ).addOnFailureListener { // 更新が失敗したときにここに来る } // 使用完了後(例えばActivity#onDestroy, Activity#onStop)は不要な更新を止める fusedLocationProviderClient.removeLocationUpdates(callback)Callback内で更新したLocationを使うので、RxもしくはFlowなどのリアクティブプログラミングの力を借りなければ、setTextとかのコードをCallbackの中に書いてしまいそうです。そうなるとメンテナンスしづらいコードになります。
また、
Activity#onDestroy
もしくはActivity#onStop
でCallbackを削除しなくてもコケはしないですが(一応Callback内でやってることによります)、常時Locationを使う必要がなければ無駄に電池を使うことになるので、止めてあげたほうがいいです。2. Coroutine / FlowでJavaの非同期APIをラップする
前回取得した最後のLocationを取得する
lastLocation: Task<Location?>
(※1)の非同期は一回限りの処理なので、Flowじゃなくて、Coroutineで対応します。ここでは
FusedLocationProviderClient
の拡張関数にして、fusedLocationProviderClient.lastLocation
を呼ぶ代わりにfusedLocationProviderClient.awaitLastLocation()
のsuspend functionで非同期の値を取得できます。suspend fun FusedLocationProviderClient.awaitLastLocation(): Location? = suspendCancellableCoroutine<Location> { continuation -> lastLocation.addOnSuccessListener { location -> continuation.resume(location) }.addOnFailureListener { e -> continuation.resumeWithException(e) } }使うときは例えばこんな感じで使います。
lifecycleScope.launch { try { val location = fusedLocationProviderClient.awaitLastLocation() ... } catch (e: Exception) { Log.d(TAG, "Unable to get location", e) } }※1: ここのCodeLabのコードが間違っているようです。CodeLabはNull非許容型で書いていましたが、Successの場合でもnullが帰ってくることがあるそうです2
リアルタイムのLocation情報を取得する
リアルタイムで定期的にデータが流れてくるので、ここはFlowの出番です。使用するBuilderは
callbackFlow<T>
です。fun FusedLocationProviderClient.locationFlow() = callbackFlow<Location> { val callback = object: LocationCallback() { override fun onLocationResult(result: LocationResult?) { result ?: return for (location in result.locations) { offer(location) // emit location into the Flow using ProducerScope.offer } } } requestLocationUpdates( createLocationRequest(), callback, Looper.getMainLooper() ).addOnFailureListener { e -> close(e) // in case of error, close the Flow } awaitClose { removeLocationUpdates(callback) // clean up when Flow collection ends } }ここでのポイントは、
1. Flowが閉じられたらawaitClose
でデータの更新を止める
2. 例外が投げられたらFlowを止める
3. (そしてFlowの性質上、購読する人がいなくなれば自動的に閉じられるので、リソースリークにならない)使う側は例えばActivity内で購読する場合はこうなります。
override fun onCreate(savedInstanceState: Bundle?) { // よくないコード。後述 lifecycleScope.launch { fusedLocationClient.locationFlow() .catch { e -> // 例外処理 } .collect { location -> // 成功時のUIの更新など } } }これでUIを更新する処理とLocationを非同期で取得する処理に分けられたので、スッキリ書けました。
ただし、上記のコードには実は一点問題が残っています。lifecycleScope.launch
はActivity#onDestroy
が呼ばれるまで死なないので、Activityの画面が見えなくても(例えばバックグラウンドに移したなど)動き続けて、無駄にflowから値を取得し続けます。クラッシュはしないですが、無駄に電力を消耗しています。
間違い。確かにlaunch
の代わりに[Lifecycle.State.STARTED]
3の時しか動かないlaunchWhenStarted
を使えばこの問題を回避できます4。launchWhenStarted
は[Lifecycle.State.STARTED]
時にしか発動しないですが、Activityが死ななかったらlifecycleScope
自体が死ぬわけじゃないので、Flowはキャンセルされませんし、awaitClose
も呼ばれません。LiveDataを購読する
問題を回避するために
Flow<T>.asLiveData()
を使ってFlowをLiveDataに変換して使えば、
Flowの生存期間・ライフサイクルの管理をLiveDataが自動でやってくれます。
[Lifecycle.State.STARTED]
の状態じゃなくなればupstream flowを閉じて5くれる[Lifecycle.State.STARTED]
の状態に戻ればもう一度upstream flowを実行してくれるこのため、View側で使うFlowでしたら、全部
Flow<T>.asLiveData()
でLiveDataに変換してから使ったほうが良いでしょう。override fun onCreate(savedInstanceState: Bundle?) { fusedLocationClient.locationFlow() .conflate() // 購読する側の処理が遅かったら値を破棄する。意味がわからなければ読み飛ばして大丈夫 .catch { e -> // 例外処理 } .asLiveData() // ★ Flow<T>をLiveData<T>に変換する .observe(this) { location -> // 成功時のUIの更新など } }完成したコード
CodeLabのサンプルなので、すでにGitHubにあります。
このCodeLabすごく良いので、ぜひやってみてください。一応このサンプルではViewModelを使ってないし、
awaitLastLocation()
とlocationFlow()
を別々で取得してくるので、シンプルに書けてない問題が残っています。もう少し改造してきれいに書けるサンプルを別途上げる予定です。続きの記事を書きました。よかったら見てください。
Developers - Request updates https://developer.android.com/training/location/request-updates ↩
Developers - Get the last known location https://developer.android.com/training/location/retrieve-current ↩
Developers - Handling Lifecycles with Lifecycle-Aware Components https://developer.android.com/topic/libraries/architecture/lifecycle ↩
Developers - Use Kotlin coroutines with Architecture components https://developer.android.com/topic/libraries/architecture/coroutines ↩
FlowLiveData.kt - https://android.googlesource.com/platform/frameworks/support/+/androidx-master-dev/lifecycle/lifecycle-livedata-ktx/src/main/java/androidx/lifecycle/FlowLiveData.kt#110 ↩
- 投稿日:2020-04-09T13:03:33+09:00
失われたAndroid Device Monitor
課題
- unity(などのAndroid Studioを直接触らない開発環境)で、Android実機のデバッグログのチェックに、Device Monitorを利用していました。
- Android Studio 3.2以降に更新したら、Device Monitorが使えなくなりました。
対処
- Android Studioに統合されている"Logcat"を使います。
- メニュー View > Tool Windows > Logcat
- ツールバーでも切り替えられます。
- ログのフィルタリングも可能です。
- スクリーンショットや動画も撮れます。
公式ドキュメント
Android Device Monitor は、Android Studio 3.1 でサポートが終了し、Android Studio 3.2 からは削除されています。Android Device Monitor で使用できる機能は、新機能に置き換えられました。以下の表に基づいて、サポートが終了した機能や削除された機能の代わりに使用できる機能をご確認ください。