20201108のAndroidに関する記事は10件です。

path_providerをAndroidで利用するとエラーが発生してしまう

Flutterにおけるストレージのパス取得のためのプラグインpath_proivderがAndroidで動作しない問題でハマったので解決策をメモしておきます。

事象

path_providerプラグインgetTemporaryDirectory()などのメソッドをAndroidで実行すると、以下の例外が発生していました。

MissingPluginException(No implementation found for method getTemporaryDirectory on channel plugins.flutter.io/path_provider)

解決策

該当プロジェクトでは、ネイティブのUIをそのまま組み込むことができるPlatformViewを利用していますが、AndroidのネイティブコードにおけるPlatformViewを登録する場所で、overrideしたメソッドの親メソッドを呼び出していないことが原因でした。

MainActivity.kt
class MainActivity: FlutterActivity() {
    override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
        // 以下1行を記述していなかった
        super.configureFlutterEngine(flutterEngine)

        flutterEngine
                .platformViewsController
                .registry
                .registerViewFactory("<platform-view-type>", NativeViewFactory())
    }
}

補足

公式のPlatformViewのサンプルには、superメソッドの呼び出しが記述されていないので、そのままコピペすると発生してしまいます。

詳しい原因は調べられていないですが、親メソッドの呼び出しがスキップされることで、プラグインで利用するネイティブ側のメソッド登録が実行されないために、事象が発生してしまうと考えられます。

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

#37 Kotlin Koans Builders/String and map builders 解説

1 はじめに

Kotlin公式リファレンスのKotlin Koans Builders/String and map buildersの解説記事です。

Kotlin Koansを通してKotlinを学習される人の参考になれば幸いです。

ただし、リファレンスを自力で読む力を養いたい方は、
すぐにこの記事に目を通さないで下さい!

一度各自で挑戦してから、お目通し頂ければと思います:fist:

2 StringBuilderを用いた文字列の連結

Kotlinにおける文字列の連結方法の1つにStringBuilderインスタンスを用いる方法があります。

以下の手順で利用します。

  1. StringBuilderインスタンスを生成する。
  2. StringBuilderインスタンスがappend()を呼び出して、引数として連結したい文字列を渡す。
  3. StringBuilderインスタンスがtoString()を呼び出して、戻り値として連結した文字列を返す。

例を見てみましょう。

Stringbuilder_Example
fun main(){
  val sb = StringBuilder()
  sb.append("G")
  sb.append("o")
  sb.append("o")
  sb.append("d")
  println(sb.toString())
}

上の例の実行結果は、Goodとなります。

3 Builders/String and map builders の解説

Kotlin Koans Builders/String and map buildersの解説です。
随時本サイトの内容を引用させていただきます。

本文とコードを見てみましょう。

Extension function literals are very useful for creating builders, e.g.:

Example
fun buildString(build: StringBuilder.() -> Unit): String {
    val stringBuilder = StringBuilder()
    stringBuilder.build()
    return stringBuilder.toString()
}

val s = buildString {
    this.append("Numbers: ")
    for (i in 1..3) {
        // 'this' can be omitted
        append(i)
    }
}

s == "Numbers: 123"

Add and implement the function 'buildMutableMap' with one parameter (of type extension function) creating a new HashMap, building it and returning it as a result. The usage of this function is shown below.

String_and_map_builders
import java.util.HashMap

/* TODO */

fun usage(): Map<Int, String> {
    return buildMutableMap {
        put(0, "0")
        for (i in 1..10) {
            put(i, "$i")
        }
    }
}

usage()を呼び出すと、Int型のkey/String型のvalueが格納されたMapが返ります。
このMapを生成するのに、builderMutableMap()を用いるのでこの関数を適切に実装していきます。

buildMutableMap()にはput()という関数を渡し(受け取る引数名をbuildとします。)、Int型のkey/String型のvalueが格納されたHashMapを返す必要があるので以下のような実装になります。

String_and_map_builders
fun <Int, String> buildMutableMap(build: HashMap<Int, String>.() -> Unit): Map<Int,String>

HashMap< Int, String>という実装になっているのは、put()を呼び出すのがHashMapインスタンスだからですね。

また、buildMutableMap()の戻り値がMap型になっていますが、もちろんHashMap型でもOKです。

次に、build()を呼び出すためのHashMapインスタンスを生成します。
生成したHashMapインスタンスがbuild()を呼び出し、戻り値として返せばOKです。

したがって、最終的な実装は以下のようになります。

String_and_map_builders
import java.util.HashMap

fun <Int, String> buildMutableMap(build: HashMap<Int, String>.() -> Unit): Map<Int, String> {
    val map = HashMap<Int, String>()
    map.build()
    return map
}

fun usage(): Map<Int, String> {
    return buildMutableMap {
        put(0, "0")
        for (i in 1..10) {
            put(i, "$i")
        }
    }
}

4 最後に

次回はKotlin Koans Builders/The function applyの解説をします:muscle:

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

【Android】SpinnerをXmlのみで初期選択値を決める【Databinding&BindingAdapter】

Android 開発において、Spinnerというものがあります。

image.png

Webではselectが挙げられると思われます。

selectでは、option要素にて、

<select name="example">
<option value="サンプル1">サンプル1</option>
<option value="サンプル2">サンプル2</option>
<option value="サンプル3" selected> サンプル3</option>
</select>

selectedを指定すれば、自動でサンプル3に選択されますが、

Androidの場合、初期値をXmlで指定することができなさそうでした。(もしあったら教えてほしい(´;ω;`))

ゆえに何を使うかと調べたところ、

Activity.kt
val spinner: Spinner = findViewById(R.id.spinner)
spinner.setSelection(positon)

こんな感じでActivityやFragmentにて、Spinnerを呼び出しsetSelection()メソッドを呼び出し、インデックスを指定するそうです。

しかし、自分はDataBindingやViewModelを勉強しだしており、ちょっと、ここにベタベタ書きたくない。。と思ってしまった。

ということでDataBindingを使った方法で切り抜けます。

やり方

https://developer.android.com/topic/libraries/data-binding/binding-adapters?hl=ja

1. Moduleを使えるようにする

まず、DataBindingとViewModelを実装できるようにします。

https://developer.android.com/topic/libraries/data-binding/start?hl=ja
↑DataBindingのスタートガイド
https://developer.android.com/jetpack/androidx/releases/lifecycle?hl=ja
↑LifecycleのModuleの依存関係

こちらを参照して、build.gradle(app)を変更して使える状態にします。

2. Xmlを変更

<Spinner
            android:id="@+id/spinner_one"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_margin="30dp"
            android:OnItemSelected="@{ここに数値}"

ここのOnItemSelectedの中に、リスト何番目を選択してほしいか書いていきます。

現状でBuildしてもXmlのエラーが出ます。

3.メソッドを実装

https://developer.android.com/topic/libraries/data-binding/binding-adapters?hl=ja
こちらはBindingAdapterのメソッドの話があります。これを見れば、正直僕より参考になります。
僕は、カスタムロジックでやったので、Kotlin-kaptをapplyしなきゃでした。

build.gradle(app)
apply plugin: 'kotlin-kapt'

その後、適当にファイルでも作って、メソッドを実装していきます。

BindingAdapters.kt
BindingAdapter("android:OnItemSelected")
fun onItemSelected(view: Spinner, position: Int) {
    view.setSelection(position)
}

こうすれば、FragmentやActivityに直接書き込むことはないので、ちょっとキレイかもしれません。

んで実際にBuildしてみると、指定した数値が最初に選択済になっていると思います!

DataBindingでやることで、ViewModel内で色々計算させて、ViewModelのメンバから参照するやり方もできるので、本来Fragmentで書いちゃうことを更に減らせちゃいます。

とっくに既知の技術ですが、自分的によく使いそうな手法だったので書いておきました。

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

【Android/Java】Fragmentに戻るボタンを設置する

はじめに

フラグメントでの戻るボタンの設置において、
親Activity内で定義して呼び出す書き方にしていたのですが、
Activityに置かずに各Fragmentから直接中身を呼び出すほうがいい
と、ベテランエンジニアから指摘をいただいたので備忘録として両パターン残しておきます。

①MainActivityから呼び出す方法

MainActivity.java
public class MainActivity extends AppCompatActivity {

//  戻るボタンを定義
    public void setupBackButton(boolean enableBackButton) {
        ActionBar actionBar = getSupportActionBar();
        actionBar.setDisplayHomeAsUpEnabled(enableBackButton);
    }
}
Fragment1.java
 // onCreateView内で呼び出す
 MainActivity activity = (MainActivity) getActivity();
 activity.setupBackButton(true);
 setHasOptionsMenu(true);  // FragmentでActionBarを使うために必要な設定
Fragment2.java
 // onCreateView内で呼び出す
 MainActivity activity = (MainActivity) getActivity();
 activity.setupBackButton(false);

②Fragmentから直接呼び出す方法

Fragment1.java
 // onCreateView内で呼び出す
 .setupBackButton()
 setHasOptionsMenu(true);  // FragmentでActionBarを使うために必要な設定

 // Fragmentクラス内で定義
 public void setupBackButton() {
        AppCompatActivity activity = (AppCompatActivity) getActivity();
        ActionBar actionBar = activity.getSupportActionBar();
        actionBar.setDisplayHomeAsUpEnabled(true);
  }

※下記はFragment1から遷移したときに戻るボタンが表示されてしまうのを防ぐために記載してます。
必要可否は各自判断してください。

Fragment2.java
 // onCreateView内で呼び出す
 .unSetupBackButton()

 // Fragmentクラス内で定義
 public void unSetupBackButton() {
        AppCompatActivity activity = (AppCompatActivity) getActivity();
        ActionBar actionBar = activity.getSupportActionBar();
        actionBar.setDisplayHomeAsUpEnabled(false);
  }

違い

ぱっと見た感じ同じですが、Activityの取得先が違います!
①MainActivity activity = (MainActivity) getActivity();
②AppCompatActivity activity = (AppCompatActivity) getActivity();

特定のActivity(今回だとMainActivity)が前提の書き方はやめたほうがいいとのことです。
前提となるクラス(自分で定義したクラス)が増えるほどコードは安全な変更がしづらくなるからです。

取得先がAppCompatActivityであれば継承元なので変更されることはほぼないし安全性が高いということでしょうかね。
先を見据えた実装ができる強強エンジニアになりたいです。

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

TargetSDK29 対象範囲別ストレージ対応

はじめに

2020/11/2から、AndroidアプリのアップデートにはTargetSDK29が必須になりました。
TargetSDK29対応の一つに対象範囲別ストレージというものがあり、ファイルへのアクセスが厳格化されました。
この対応の際にAndroidのファイルシステムや具体的な対応方法に苦労したので、知見を共有しておきます。
コード例はすべて画像を対象にしたものです。保存したいファイルに応じて、適宜読み替えてください。

対象範囲別ストレージについての公式の説明はこちら:
https://developer.android.com/training/data-storage/files/external-scoped?hl=ja

対応方針

具体的な対応方法に入る前に、まずはファイルの保存先について見直しましょう。
保存先が外部領域か内部領域か、アプリ専用領域か公開領域かで色々と異なります。

外部かつアプリ専用領域 外部かつ公開領域 内部かつアプリ専用領域
アプリ削除時にファイルが 消える 消えない 消える
ユーザーからファイルアクセスが 可能 可能 不可能
Permissionが 必要 必要 不要
TargetSDK29からパスを指定して読み書きが 不可能 不可能 可能

※内部かつ公開領域は存在しません

外部領域

ここで指す外部領域とは必ずSDカードとは限りません。
メディアを共有するためのストレージ、くらいの認識です。

参考:
https://developer.android.com/reference/android/os/Environment#getExternalStorageDirectory()

Note: don't be confused by the word "external" here. This directory can better be thought as media/shared storage. It is a filesystem that can hold a relatively large amount of data and that is shared across all applications (does not enforce permissions). Traditionally this is an SD card, but it may also be implemented as built-in storage in a device that is distinct from the protected internal storage and can be mounted as a filesystem on a computer.

他のアプリからファイルを使えるようにする必要がある場合は外部領域に保存する必要があります。

外部かつアプリ専用領域に保存する方法

getExternalFilesDir()を使用します。
この方法を用いて保存したファイルは以下のようなパスになります。
/storage/emulated/0/Android/data/アプリのパッケージ名/files/Pictures/example.jpg
※パスはイメージ用に示していますが、端末によって異なってくることに注意してください。

ImageUtil.kt
    /**
     * アプリ外部に画像を書き出す
     * @param fileName ファイル名
     * @param bmp 書き出したい画像のBitmap
     * @param quality 書き出すJPEG画像の品質
     */
    fun saveImageToExternal(context: Context, fileName: String, bmp: Bitmap, quality: Int) {
        val file = File(context.getExternalFilesDir(Environment.DIRECTORY_PICTURES), fileName)
        try {
            val outputStream = FileOutputStream(file)
            bmp.compress(Bitmap.CompressFormat.JPEG, quality, outputStream)
            outputStream.close()
        } catch (e: FileNotFoundException) {
            e.printStackTrace()
        } catch (e: IOException) {
            e.printStackTrace()
        }
    }

外部かつ公開領域に保存する方法

MediaStoreを使う方法とストレージアクセスフレームワークを使う方法の2つあります。
MediaStoreを使う方法だとコードがやや煩雑ですが、UIが自由です。アプリ内のボタンを押してすぐ保存、などの処理がしたい場合はこちらです。
ストレージアクセスフレームワークを使うとコードが簡単ですが、UIがAndroid指定のものになります。

ストレージアクセスフレームワークについての公式の説明:
https://developer.android.com/guide/topics/providers/document-provider?hl=ja

MediaStore

ContentProvider経由でUriを取得して、そのUriに書き込みます。
API29からはMediaStore.Images.ImageColumns.RELATIVE_PATHでディレクトリを指定できますが、28以下だとMediaStore.Images.ImageColumns.DATAを使う必要がありそうです。
良いコードの書き方があればぜひ教えて下さい。

パスの例
/storage/emulated/0/Pictures/HOGE/example.jpg
※パスはイメージ用に示していますが、端末によって異なってくることに注意してください。

ImageUtil.kt
    /**
     * 外部書き出し用のURIを取得する
     * @param fileName ファイル名
     */
    private fun getUri(context: Context, fileName: String): Uri? {
        val contentResolver = context.contentResolver
        val contentValues = ContentValues().apply {
            // 相対パスのルートにはDCIMかPicturesの指定が必須
            // java.lang.IllegalArgumentException: Primary directory HOGE not allowed for content://media/external/images/media; allowed directories are [DCIM, Pictures]
            if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
                put(MediaStore.Images.ImageColumns.RELATIVE_PATH, "Pictures/HOGE")
            } else {
                val path = Environment.getExternalStorageDirectory().path + "/Pictures/HOGE/"
                val dir = File(Environment.getExternalStorageDirectory().path + "/Pictures", "HOGE")
                if(!dir.exists()) {
                    dir.mkdir()
                }
                put(MediaStore.Images.ImageColumns.DATA, path + fileName)
            }
            put(MediaStore.Images.ImageColumns.DISPLAY_NAME, fileName)
            put(MediaStore.Images.ImageColumns.DATE_TAKEN, System.currentTimeMillis())
        }
        return contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)
    }

    /**
     * アプリ外部に画像を書き出す
     * @param fileName ファイル名
     * @param bmp 書き出したい画像のBitmap
     * @param quality 書き出すJPEG画像の品質
     */
    fun saveImageToExternal(context: Context, fileName: String, bmp: Bitmap, quality: Int = 100) {
        val uri = getUri(context, fileName)
        uri?.let {
            try {
                val outputStream = context.contentResolver.openOutputStream(it)
                bmp.compress(Bitmap.CompressFormat.JPEG, quality, outputStream)
                outputStream?.close()
            } catch(e: FileNotFoundException) {
                e.printStackTrace()
            } catch (e: IOException) {
                e.printStackTrace()
            }
        }
    }

ストレージアクセスフレームワーク

下記のスクリーンショットのような画面が開きます。

intent.putExtra(Intent.EXTRA_TITLE, "")で保存するファイルのデフォルト名が設定できます。
保存先はユーザーが選べますが、デフォルトのまま保存したときのパスは以下のようになります。
/storage/emulated/0/Download/example.jpg
※パスはイメージ用に示していますが、端末によって異なってくることに注意してください。

MainActivity.kt
    companion object {
        const val SAVE_IMAGE = 1000
    }

    var bmp: Bitmap? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        // setContentViewなど。省略

        binding.button3.setOnClickListener {
            bmp = ImageUtils.makeBitmap(128, 128, Color.YELLOW)
            val intent = Intent(Intent.ACTION_CREATE_DOCUMENT)
            intent.type = "image/*"
            intent.putExtra(Intent.EXTRA_TITLE, "example.jpg")
            startActivityForResult(intent, SAVE_IMAGE)
        }
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        when(requestCode) {
            SAVE_IMAGE -> {
                if(resultCode == RESULT_OK) {
                    try {
                        data?.data?.let {
                            val outputStream = contentResolver.openOutputStream(it)
                            bmp?.compress(Bitmap.CompressFormat.JPEG, 100, outputStream)
                            outputStream?.close()
                        }
                    } catch(e: FileNotFoundException) {
                        e.printStackTrace()
                    } catch(e: IOException) {
                        e.printStackTrace()
                    }
                }
            }
        }
    }

参考:
https://akira-watson.com/android/action_create_document.html

内部領域に保存

内部領域はその名の通り、端末内蔵ストレージを指します。
内部領域でもキャッシュディレクトリとアプリ固有ディレクトリの2つがあります。
突然消されてもいいファイルはキャッシュディレクトリ、そうでないファイルはアプリ固有ディレクトリに保存しましょう。
キャッシュディレクトリに保存したファイルはストレージが逼迫しない限りは消されないようなので(未確認)、適宜削除処理を入れることを推奨します。

パスの例

  • アプリ固有ディレクトリの場合
    • /data/user/0/アプリのパッケージ名/files/example.jpg
  • キャッシュディレクトリの場合
    • data/user/0/アプリのパッケージ名/cache/example.jpg

※パスはイメージ用に示していますが、端末によって異なってくることに注意してください。

キャッシュディレクトリについての仕様:
https://developer.android.com/reference/android/content/Context#getCacheDir()

ImageUtil.kt
    /**
     * アプリ専用ディレクトリに画像を書き出す
     * @see saveImageToInternal
     */
    fun saveImageToAppFileDir(context: Context, fileName: String, bmp: Bitmap, quality: Int = 100) {
        saveImageToInternal(context.filesDir, fileName, bmp, quality)
    }

    /**
     * アプリ専用キャッシュディレクトリに画像を書き出す
     * @see saveImageToInternal
     */
    fun saveImageToAppCacheDir(context: Context, fileName: String, bmp: Bitmap, quality: Int = 100) {
        saveImageToInternal(context.cacheDir, fileName, bmp, quality)
    }

    /**
     * アプリ内部に画像を書き出す
     * @param directory 書き出したいディレクトリ
     * @param fileName ファイル名
     * @param bmp 書き出したい画像のBitmap
     * @param quality 書き出すJPEG画像の品質
     */
    private fun saveImageToInternal(directory: File, fileName: String, bmp: Bitmap, quality: Int) {
        val file = File(directory, fileName)
        try {
            val outputStream = FileOutputStream(file)
            bmp.compress(Bitmap.CompressFormat.JPEG, quality, outputStream)
            outputStream.close()
        } catch (e: FileNotFoundException) {
            e.printStackTrace()
        } catch (e: IOException) {
            e.printStackTrace()
        }
    }

おわりに

この記事では、対象範囲別ストレージを機にファイルの保存先の見直しが行えるようにAndroidの保存先についての説明と具体的な保存方法について示しました。
外部領域/内部領域とは何か、書くディレクトリに付与されてる権限はLinuxシステム上でどうなっているかなどについては理解が曖昧なので示しませんでした。
誤字脱字などの問題点や記事をより良くするための情報があればご指摘ください。
参考になれば幸いです。

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

【React Native】生成したreact-nativeプロジェクトがiOSで起動できなかった件

はじめに

React Nativeを試してみようとインストールしてみたものの、生成したreact-nativeプロジェクトがiOSで正しく動かなかったので、その対処メモ。

実行環境

  • Xcode 12.1
  • Android Sudio 3.2.1
  • macOS Catalina(10.15.7)

React Nativeのインストール

環境設定は、記事に日付も新しいのでこちらのサイトを参考にしながら行いました。

※私の環境には、Homebrew、Nodejs、Xcode、Cocoapods、Android Studioは導入済みなのでインストールしていません。

1.Watchmanのインストール

brew install watchman

2.React Native CLIのインストール

npm install -g react-native-cli

3.JDK(Java Development Kit)のインストール

brew tap AdoptOpenJDK/openjdk
brew cask install adoptopenjdk8

iOSシミュレーターで正しくアプリが起動しない

セットアップが終わったので、iOSアプリを起動してみます。

cd SampleApp
npm run ios

simulatorが起動し、アプリがロードされて。。。
と、参考サイトとは違い、こちらのサイトのような赤い画面にエラーが。メッセージはURL以降は違っててこんな感じ。

Simulator Screen Shot - iPhone 11 - 2020-11-08 at 12.24.58.png

で、アプリ起動時のコンソールを見てみると、watchmanで以下のようなメッセージが出てました。

020-11-01T21:54:49,874: [0x11ce00dc0] while computing sockname: failed to create /usr/local/var/run/watchman/c_geru-state: No such file or directory

Watchman:  watchman --no-pretty get-sockname returned with exit code=1, signal=null, stderr= 2020-11-01T21:54:49,874: [0x11ce00dc0] while computing sockname: failed to create /usr/local/var/run/watchman/c_geru-state: No such file or directory

色々調べてみると、こちらのサイトによれば、High Sierraから/usr/local の権限変更が出来なくなったらしい。試してみると、確かにターミナルでも、Finderでも権限がないと怒られて変更できない。

遡ってインストール時のログを確認すると、Watchmanのインストール時に同じ「No such file or directory」のログが表示されていました。おそらくWatchmanインストール/起動時に必要なディレクトリが生成できず、アプリが正常に起動できなかったようです。

同じサイトによれば「/usrl/local/以下のディレクトリでは権限変更、作成が出来る」と書かれていたので、/usr/local/var/ のadminの権限を読み/書きに変更しました。

スクリーンショット 2020-11-08 12.30.59.png

無事プロジェクトが表示されました!

Simulator Screen Shot - iPhone 11 - 2020-11-08 at 12.23.24.png

Androidでの起動

Androidは、事前にシミュレーターを起動する必要があるとのことで、「Android Studioの起動画面右下の「Configure」>「AVD Manager」を選択して...」と書かれているんだけど、AVD Managerが見当たらない。

スクリーンショット 2020-11-08 12.14.13.png
スクリーンショット 2020-11-08 12.14.06.png

いろいろ調べてみたけど、なぜ表示されていないのかわからず...。

起動画面の「Start a new Android Studio project」 から新規プロジェクトを生成してみると、AVD Manager自体はインストールされていたので、ここから起動してシミュレーターを立ち上げてから

npm run android

で無事起動しました。

まとめ

/usr/local の権限は意外な落とし穴でした。同じようなトラブルが出てる方の参考になれば。

以下、参考リンクです。

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

iOS/Android の OS のシェア率を比較するのに StatCounter がめちゃくちゃく便利な件

はじめに

StatCounter
Tracks the Usage Share of Search Engines, Browsers and Operating Systems including Mobile from over 10 billion monthly page views.

StatCounter は モバイルを含む、検索エンジン、ブラウザ、OSの使用シェアの統計データを集計するサイトのことです。

iOS/Android の OS のシェア率の比較などを確認するときに、操作が直感的でとても役にたったので共有します。

よく使いそうなグラフをサンプルとして添付します。(2020-11-08現在)

使い方

「Edit Chart Date」 をクリックする。

スクリーンショット 2020-11-08 11.43.25.png

「Statistic」、「Region」、「Chart Type」、「Period」を設定して、「View Chart」をクリックする。

スクリーンショット 2020-11-08 11.46.57.png

グラフが表示される。

スクリーンショット 2020-11-08 12.01.11.png

以上。

サンプル

現在の iOS/Android のシェア率 棒グラフ (日本)

ivsA_Japan.png

現在の iOS/Android のシェア率 棒グラフ (世界)

ivsA_World.png

現在の iOS/Android のシェア率 世界地図

ivsA_World_map.png

過去10年の iOS/Android のシェア率 棒グラフ (日本)

ivsA_World_Japan_line.png

現在の iOS のシェア率 棒グラフ (日本)

iOS_bar.png

過去1年間の iOS のシェア率 線グラフ (日本)

iOS_line.png

グラフからわかること

  • iOS VS Android
    • iOS の方がシェア率が高いのは日本、オーストラリア、アメリカ、カナダ、ヨーロッパの一部ぐらい。
    • 世界の平均に比べて日本の iOS 支持率が異常に高い
    • しかし、2019年から日本の Android 支持率が上がってきている傾向
  • iOS の中の OS シェア率
    • 日本では月単位でグラフが上下するほど iOS は頻繁にアップデートされている

ちょっとおしいところ

  • メジャーバージョン単位での iOS シェア率がわからない
    • マイナーバージョン含めてのシェア率はわかるが、 iOS 12 vs iOS 13 vs iOS 14 のようなシェア率はわからない
  • 細かいデバイス別のシェア率がわからない
    • 例えば Apple vs Samsung vs Huawei ようなシェア率はわかるが、Apple の中の iPhone SE vs iPhone 8 のようなシェア率はわからない
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

iOS/Android の OS のシェア率を比較するのに StatCounter というサイトがかなり便利な件

はじめに

StatCounter
Tracks the Usage Share of Search Engines, Browsers and Operating Systems including Mobile from over 10 billion monthly page views.

StatCounter は モバイルを含む、検索エンジン、ブラウザ、OSの使用シェアの統計データを集計するサイトのことです。

iOS/Android の OS のシェア率の比較などを確認するときに、操作が直感的でとても役にたったので共有します。

よく使いそうなグラフをサンプルとして添付します。(2020/11/08のデータ)

使い方

「Edit Chart Date」 をクリックする。

スクリーンショット 2020-11-08 11.43.25.png

「Statistic」、「Region」、「Chart Type」、「Period」を設定して、「View Chart」をクリックする。

スクリーンショット 2020-11-08 11.46.57.png

グラフが表示される。

スクリーンショット 2020-11-08 12.01.11.png

以上。

サンプル (2020/11/08)

現在の iOS/Android のシェア率 棒グラフ (日本)

ivsA_Japan.png

現在の iOS/Android のシェア率 棒グラフ (世界)

ivsA_World.png

現在の iOS/Android のシェア率 世界地図

ivsA_World_map.png

過去10年の iOS/Android のシェア率 棒グラフ (日本)

ivsA_World_Japan_line.png

現在の iOS のシェア率 棒グラフ (日本)

iOS_bar.png

過去1年間の iOS のシェア率 線グラフ (日本)

iOS_line.png

グラフからわかること

  • iOS VS Android
    • iOS の方がシェア率が高いのは日本、オーストラリア、アメリカ、カナダ、ヨーロッパの一部ぐらい。
    • 世界の平均に比べて日本の iOS 支持率が異常に高い
    • しかし、2019年から日本の Android 支持率が上がってきている傾向
  • iOS の中の OS シェア率
    • 日本では月単位でグラフが上下するほど iOS は頻繁にアップデートされている

ちょっとおしいところ

  • メジャーバージョン単位での iOS シェア率がわからない
    • マイナーバージョンでの iOS シェア率はわかるが、 iOS 12 vs iOS 13 vs iOS 14 のようなメジャーバージョンでのシェア率はわからない
  • デバイス別のシェア率がわからない
    • 例えば Apple vs Samsung vs Huawei ようなシェア率はわかるが、Apple の中の iPhone SE vs iPhone 8 のようなシェア率はわからない

足りないデータを補うサイト

上記のおしいところを補うサイトを探しました。

メジャーバージョン単位での iOS シェア率 (by スマタブinfo)

日本のメジャーバージョンごとの iOS のシェア率は以下のサイトで確認できます。2ヶ月に一回ほど更新されているそうです。
「Home > OS」 - スマタブinfo
スクリーンショット 2020-11-08 13.48.39.png

デバイス別のシェア率 (by DeciceAtlas)

以下のサイトで日本での2019年のデバイスシェア率が確認できます。
残念ながら、データが古く(2019年)、また、ここで表すシェア率は Android 端末含めたすべてのデバイスの中でのシェア率のため、iPhone 端末のみの比較には使用できなので注意が必要です。

「The most popular smartphones in 2019」 - DeviceAtlas

スクリーンショット 2020-11-08 13.49.17.png

他にも探してみましたが、最新の iPhone 端末のシェア率のデータは見つけられませんでした。
もし、ご存知の方がいましたら、ご教授のほどよろしくお願いいたします。

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

Android 日付を取得する方法

Androidで日付を取得する方法を記録します。
思ったよりも簡単に書けました。

  fun getToday(): String {

         //表示させたい型を決める。 yは年、Mは月、dは日。
         //単位は自由に決められる。例えば、"yyyy/MM/dd"でもOK。
        val sdf = android.icu.text.SimpleDateFormat("yyyy年MM月dd日", Locale.JAPAN)

        //date型のインスタンスを作る。
        val date = Date()

        return sdf.format(date)
  }

以上です。

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

Androidでmoduleを作りアプリ側から読み込む方法

はじめに

共通で使えるライブラリやSDKとして他の人に使ってもらうためにmoduleを作る方法を簡単にメモします。
基本的にcommon的なapiの公開やよくあるview系のライブラリ化もこの手順でできます。
今回は簡単なメソッドを一つ作りアプリ側から読み込む方法を残します。

ライブラリの組み込み公式ページ

https://developer.android.com/studio/projects/android-library?hl=ja

moduleの作り方

AndroidStudioでこうして
スクリーンショット 2020-11-07 23.06.19.png

こうして
スクリーンショット 2020-11-07 23.06.32.png

こうです
スクリーンショット 2020-11-07 23.06.47.png

デフォルトのmylibraryとしておきます

適当なメソッドを作る

mylibraryの下にcommonを作りました。コードは適当にトップレベル関数として作っておきます。
単にhelloをもらうだけです

package com.example.mylibrary.common

fun fetchHelloMessage() :String {
    return "hello"
}

 アプリ側で読み込み

dependenciesへ下記コードを書き読み込みます

implementation project(":mylibrary")

syncしてください

利用してみる

importします

import com.example.mylibrary.common.fetchHelloMessage

あとは呼ぶだけ

fetchHelloMessage()

最後に

viewも同じように取り込めるのでsdkでviewを提供する場合もこんな雰囲気です。
gradleでprojectとしてmoduleを読み込むのではなく、他のライブラリのように組み込む場合はmavenリポなどに配置する必要があります。
今時は、ホスティングはJitPackがナウいらしい(使ったことないので詳しくはわかりませんがgithubへpushしてgradleによきに取り込めるらしい)

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