diadia

興味があることをやってみる。自分のメモを残しておきます。

緯度経度情報を取得する

アンドロイド端末から座標データを取得する

必要な要素__パーミッション なんのクラスメソッドを使って取得するのか? ドキュメントの位置はどこか?

座標データの取得方法

概要

  1. パーミッションマニフェストファイルに追記する。

  2. 座標データを取得するにはFusedLocationProviderClient#getLastLocation()メソッドを使う。

手続きは、FusedLocationProviderClientインスタンス(クライアント)を生成し、そのインスタンスでgetLastLocation()メソッドを実行するだけだ。

参考:

現在地の住所を表示する  |  Android デベロッパー  |  Android Developers

FusedLocationProviderClient  |  Android 用 Google API  |  Google Developers

パーミッションを追記

以下をマニフェストファイルに追記する。

<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>

LocationSevicesをインストール

以下をappレベルのGradleのdependenciesに追記する

implementation 'com.google.android.gms:play-services-location:17.0.0'

クライアントの作成方法

https://developer.android.com/training/location/retrieve-current?hl=ja#play-services

lateinit var fusedLocationClient: FusedLocationProviderClient

fusedLocationClient = LocationServices.getFusedLocationProviderClient(this)

位置情報の取得

val coordinates = fusedLocationClient.lastLocation

これで座標データ(位置情報)を取得できる。

その他

マニフェストファイルに追加した情報は、dangerous レベルのパーミッションである。 パーミッションを解除するロジックを埋め込まないと座標点は取得する事ができない。

その他の方法として座標データから住所に変換する

現在地の住所を表示する  |  Android デベロッパー  |  Android Developers

カメラを使うアプリの実装方法

カメラ使用にはパーミッションが必要である

<uses-permission android:name="android.permission.CAMERA"/>
 <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

当初カメラを使う場合にはandroid.permission.CAMERAのみを使えば使えると思っていたが、それは間違いであった。カメラアプリで撮影した画像を保存すること、また保存するディレクトリを作成する際にandroid.permission.WRITE_EXTERNAL_STORAGEが必要になる。

パーミッションのチェック

カメラの場合はマニフェスト.xmlファイルにパーミッションについての旨を記述する他、アプリ起動中にユーザーに聞く事が必要になる。 またandroid6以上から聞く必要があるようで、それ以前は聞く必要がないようだ。

撮った画像を外部ストレージに共有する方法

撮った写真を共有するためには、外部ストレージに保存し、その外部ストレージから画像を読み込む必要があることが分かった。 外部ストレージの保存場所としてuriを使う事がわかった。

uriに関してはUriクラスを使って、例えば以下のように

val uri:Uri = Uri.parse("file/path")

uriを生成することができる。しかしながらこのuriを使っても外部ストレージから画像を読み込むことができないようだ。

uriはcontentProviderを使わないと他のアプリと画像を共有できない縛りがある事がわかった。 つまりcontentProviderで作成したuriを使わないと他のアプリと共有することができない。

ここでまとめると、カメラアプリで撮った写真を自分のアプリで利用する場合には、ContentProviderで生成したuriを利用する。 uriはファイルパスなのでファイルパスは自分で生成しておく。 そしてファイルパス(Fileクラス)をContentProviderにわたす手続きとなる。 またContentProviderにはファイル専用のuriを生成するサブクラスがあるらしい。これを使うと以下のような感じでuriを生成できる。

val uri = FileProvider.getUriForFile(this, applicationContext.packageName + ".fileprovider" ,contentFile)

contentFile(Fileクラス)は以下のように生成する。

val contentFile = File("file/path")

カメラアプリの起動方法

intentを使えば良い。

var uri = "hoge"
val CODE = 200 
val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE).apply{
    putExtra(MediaStore.EXTRA_OUTPUT, uri)
    startActivityForResult(intent, CODE)
}

putExtraメソッドでMediaStore.EXTRA_OUTPUT, uriを渡しているが、このuriはFileProviderオブジェクトである。このオブジェクトを使うことでカメラアプリが保存した写真を自前アプリで扱う事ができる。 FileProviderのドキュメント:

FileProvider  |  Android Developers

撮った写真を画面に表示する場合

onActivityResultメソッドをoverrideして以下のコードをその中に記述する。

imageView.setImageURI(Uri.parse(strUri))

撮った写真を削除する場合

撮った写真の削除はつまり、スマホ本体にある画像を削除することである。 contentResolverを使えば良い。 ContentResolver  |  Android Developers

サンプルコード

GitHub - chiaki1990/CameraSample_kotlin: カメラを使う場合のパーミッション、FileProvider、写真の削除等のサンプル

FileProvider Failed to find configured root that contains のエラーが出る場合

FileProviderを使って以下のエラーが生じる

Failed to find configured root that contains

参考

Android: FileProvider Issue: Failed to find configured root that contains ... · Issue #218 · react-native-community/react-native-share · GitHub

自分の作成したxmlファイルは以下の通り。

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <external-path name="name" path="." />
</paths>

これだとrootが見つけられないと表示される。

root-pathタグに変更したらエラーを解消できた。

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <root-path name="name" path="." />
</paths>

GoogleMapにデータを表示する メモ

イメージ

写真や何かを表示するためのIntent.ACTION_VIEWを使う。 そしてGoogleMapを直接使うためにintentにgooglemapをセットする。 その後にstartActivityメソッドを実行すれば良い。

コードサンプル

val longitude = ***
val latitude = ***
val geoStrUri = "geo:" + longitude + latitude + "?z=13"
val geoUri = Uri.parse(geoStrUri)
val intent = Intent(Intent.ACTION_VIEW, geoUri)
intent.setPackage("com.google.android.apps.maps")
startIntent(intent)

この実装の特徴

この実装では自分のアプリからGoogleMapsをintentを使って起動している。それはアプリから別アプリを起動しただけであるので、googleMaps内でタップすると、GoogleMapsのアプリのロジックに従ったレスポンスが返されるだけである。 マップをタップすると、そのタップした内容に従って自作アプリのレスポンスを返すことがしたい場合にはintentではなく、GoogleMapsPlatformのAPIを利用する必要がある。

Uriについて補足

intent関係でuriオブジェクトを生成することが多い事がわかった。 例えば、カメラでとった写真をアプリケーションで表示したい場合もuriを生成しなければならなかったと思う。 このuriが何かわからなかったが、uriの作り方は共通している。

Uri#parse()メソッドに特定の文字列を渡すだけである。

これさえ理解すればあまり困ることはない。

GoogleMapをタッチして別の反応を実装するにはどうすればよいか

参考 Map Objects  |  Maps SDK for Android  |  Google Developers

GoogleMasPlatform

GoogleMasPlatformを使えば良いようだ。 Google Maps Platform  |  Google Developers これを使うにはGCPに登録が必要だった。API keyを取得し、そのkeyをマニフェストファイルにmetaタグに含めて記述する。 そしてactivityでgoogleMapsを呼び出すフラグメントを実施すればgoogleMapsの画面(GoogleMapsのアプリではない)を呼び出すことができた。

マップに座標地点(ポイント)をマーキングする

Adding a Map with a Marker  |  Maps SDK for Android  |  Google Developers

OnMapReadyCallback#onMapReady()コールバックメソッドをoverrideする。そのなかでaddMarkerメソッドを実装する。

位置情報を取得する方法 メモ

参考

[Android] GPSで位置情報を取得するアプリを作る

Get the last known location  |  Android Developers

位置情報を取得するクラス

LocationManager使う。 fused location provider の選択肢もある。

このドキュメントに従って実装する

Get the last known location  |  Android Developers

Google Play 開発者サービスをセットアップする

Gradle.appのdependenciesに以下を追記する

implementation 'com.google.android.gms:play-services-location:17.0.0'

アプリの権限をマニフェストファイルで指定する

上記が位置情報を取得するパーミッションである。 以下の感じでマニフェストファイルに追記する。

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.google.android.gms.location.sample.basiclocationsample" >

      <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
    </manifest>

位置情報サービス クライアントを作成する

val client = LocationServices.getFusedLocationProviderClient(this)

最新の位置情報を取得する

client.lastLocation //最新の位置情報を取得

//その他の使い方として位置情報を取得後、
//リスナーを設置する事ができる
client.lastLocation.addOnSuccessListener {  
    //logic
}

Fileクラスについて

ドキュメント

File  |  Android Developers

メモ

An abstract representation of file and directory pathnames. と書いてあるようにファイルまたはディレクトリのパスを表現するもののようだ。

パーミッションの解除

パーミッションの解除

資料

アプリの権限をリクエストする  |  Android デベロッパー  |  Android Developers

システム パーミッション  |  Android デベロッパー  |  Android Developers

かんたんにまとめると...

dangerous パーミッションが必要な場合は、その都度パーミションが得られているか確認する。 パーミッションの確認は、ContextCompat.checkSelfPermission()メソッドを使う。

メモ

パーミッションには2種類ある。 それをAndroidの用語では、Normalパーミッション、Dangerousパーミッションと呼ぶようだ。

Dengerousパーミッションのリスト

  1. CALENDAR
  2. CAMERA
  3. CONTACTS
  4. LOCATION
  5. MICROPHONE
  6. PHONE

パーミッションのリストは以下を参照すること。 システム パーミッション  |  Android デベロッパー  |  Android Developers

アプリに dangerous パーミッションが必要な場合は、dangerous パーミッションが必要な操作を実行するたびにパーミッションの有無を確認する必要があり、その確認はContextCompat.checkSelfPermission()メソッドを使う。

    if (ContextCompat.checkSelfPermission(thisActivity, Manifest.permission.WRITE_CALENDAR)
            != PackageManager.PERMISSION_GRANTED) {
        // Permission is not granted
    }

ContextCompat  |  Android デベロッパー  |  Android Developers

パーミッションを得る

requestPermissions()を使ってパーミッションをリクエストするらしい。

マテリアルアイコンを作成する

vector iconからマテリアルアイコンを生成する。

マテリアルアイコンはAPIレベル21未満は使えない使用になっているらしい。 21未満でもこれを使えるようにはできるらしい。それはどのような手続きを踏むのか。

それはサポートライブラリを使うとAPIレベルが足りなくても使えるようになるらしい。

    defaultConfig {
        applicationId "com.example.todo"
        minSdkVersion 23
        targetSdkVersion 29
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"

        //アイコンをAPIレベル21未満でも使えるように設定した
        vectorDrawables.useSupportLibrary = true
    }

エラー:java.lang.IllegalArgumentException: No view found for id がFragment.kt内で生じる件

エラー内容

java.lang.IllegalArgumentException: No view found for id

解決に役立った資料

Android Fragment no view found for ID? - Stack Overflow

エラーが生じた環境

フラグメントを複数生成し、生成したフラグメントを起動する際にエラーが生じた。またスマホの他、タブレットに対応する為フラグメント.ktファイル内で別のflamelayoutを使用することを指定していた。

エラーが生じた際のコード

EditActivity.kt

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_detail)
    setSupportActionBar(toolbar)


    supportFragmentManager.beginTransaction()
        .add(R.id.container_detail, DetailFragment.newInstance(title, deadline, taskDetail, isCompleted))
        .commit()
    }

activity_edit.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".EditActivity">

    <com.google.android.material.appbar.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:theme="@style/AppTheme.AppBarOverlay">

        <androidx.appcompat.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:background="?attr/colorPrimary"
            app:popupTheme="@style/AppTheme.PopupOverlay" />

    </com.google.android.material.appbar.AppBarLayout>
    <!-- ここがエラー原因 -->
    <include layout="@layout/content_edit" /> 

    <com.google.android.material.floatingactionbutton.FloatingActionButton
        android:id="@+id/fab"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom|end"
        android:layout_margin="@dimen/fab_margin"
        app:srcCompat="@android:drawable/ic_dialog_email" />

</androidx.coordinatorlayout.widget.CoordinatorLayout>

エラー対処

Activityでフラグメントを起動する際にinclude layout="@layout/content_edit" ではなく、include layout="@layout/content_detail"に変更しなければならない。 フラグメントを生成すると、container_hoge.xml内のincludeタグはcontent_hogeで生成される。ここの内容とFragmentoを生成するロジック(Activity)で使う内容が一致していなければならない。

フラグメント間でデータを渡す方法

フラグメントでデータを渡す方法

Applicationを継承したcompanion objectを使う方法

https://github.com/chiaki1990/FragmentSample1

あらたなインターフェースを別クラスとして準備する方法

https://github.com/chiaki1990/FragmentSample2

フラグメントのまとめ

フラグメントを外す場合のコード

フラグメント.kt内で以下のコードを実行すると、Activityに対してフラグメントを外す事ができる。 それはつまりActivityのframeLayout部分からフラグメントが削除され白い空間となる。

fragmentManager?.beginTransaction()?.remove(this)?.commit()

Fragment  |  Android デベロッパー  |  Android Developers

またFragment#fragmentManagerだけれどもFragmentActivity#supportFragmentManagerと違いがある。FragmentActivityのサブクラスにAppCompatActivityである。

アクティビティを落とす場合のコード

フラグメント.kt内で以下のコードを実装するとフラグメントに紐付いているアクティビティを落とすことができる。

activity.finish()

Fragment  |  Android デベロッパー  |  Android Developers

関連記事

フラグメントでオプションメニューを実装する - diadia

フラグメント間でデータを渡す方法 - diadia

エラー対処:java.lang.IllegalStateException: menu.findItem(R.id.action_settings) must not be null

エラーコード

java.lang.IllegalStateException: menu.findItem(R.id.action_settings) must not be null

エラーが出た環境

フラグメントを使用してメニューを描画する場合にエラーが発生した。フラグメント.ktファイルにはonCreateOptionMenuをオーバーライドしている。 フラグメント内で取得しようとするR.id.action_settingsはmenu.XMLファイルは存在している。

エラーの対処方法

フラグメントにくっつけるアクティビティでは、オーバーライドしたonCreateOptionMenuを実装せず、フラグメント.ktのみでonCreateOptionMenuを実装していた。そこでアクティビティの方も実装したらエラーが出なくなった。

得られた知見

フラグメントでonCreateOptionMenuをオーバーライドして実装する場合には、アクティビティの方もonCreateOptionMenuをオーバーライドする必要がある。

追記

MainActivity以外で作成したBasic ActivityでonCreateOptionMenuをoverrideすると

menuInflater.inflate(R.menu.menu_main, menu)

を忘れることがある。これを忘れると上記のエラーが生じる。

Android メニュー実装のまとめ

俯瞰

Androidのメニューを実装するとは、画面上部にあるツールバーを実装することを指す。

このツールバーを実装するに際し、Activityをどう作成するかによって多少実装方法が変わってくるので注意する。 Activityをどう作成するかとは、具体的にはBasic Activityで作成するか、Empty Activityで作成するかということだ。 前者で作成すると、project内に自動的にmenuファイルが生成される。一方後者の場合には、それが生成されない。 こういった違いからメニューを実装する場合には多少の実装手順が増えたり、減ったりする違いが出てくる。

またフラグメントのロジック内でメニューを実装することもできるが、この場合も基本的に実装手順は概ね同じである。

Basic Activityでプロジェクトを開始した場合のメニュー実装方法

Empty Activityでプロジェクトを開始した場合のメニュー実装方法

メニューを実装する(EmptyActivity) - diadia

Fragmentのロジック内でメニューを実装する

フラグメントでオプションメニューを実装する - diadia

関連記事

ActionbarとToolbarの存在 - diadia