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

react-native+TypeScriptなプロジェクトでdotenvを読ませる

https://github.com/zetachang/react-native-dotenv

まずは追加。

yarn add react-native-dotenv

.babelrcに以下追加。

".babelrc"
...
  "presets": [..., "module:react-native-dotenv"],
...

次に、react-native-dotenvには型定義ファイルがないので自前でdeclareする必要がある。
./src/lib/vendor-typings.d.tsにモジュールを定義。

vendor-typings.d.ts
// declare module 'react-native-dotenv';

起動してみる。これで動くはず。

app.tsx
import * as React from 'react'
import { Text } from 'react-native'
import {
     YOUR_ENV,
} from 'react-native-dotenv'

export default = (): JSX.Element => {
    return <Text>{YOUR_ENV}</Text>
}

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

複数 View の同時アニメーションを DataBinding を用いてシンプルに実装する

複数の View を同時にアニメーションするとき、どのように実装しているでしょうか?
例えば、以下の動画のように▼ボタンを押すとドロアが開くような処理を実装するとします。

open_close4.gif

この動きを実現するためには、

  1. View A にある ▼ ボタンの回転アニメーション(rotation: 0 → 180 )
  2. View B にかけるマスクのアルファ値のアニメーション(alpha: 0 → 1)
  3. View C の拡大・縮小アニメーション(height: 0dp → 200dp)

という 3 つのアニメーションを同時に行う必要があります。
3つそれぞれの View に対して ViewCompat.animate を用いてアニメーションを実装すると、3つのアニメーションの同期が保たれるように実装する必要が出てきます。ボタンが連打されてアニメーションが途中で中断されることなども考慮するとソースコードが長く複雑になってしまいがちです。

このような悩みを解決するためにはどうすれば良いか考えてみました。
この記事では、DataBinding を用いた実装手法を紹介します。
サンプルで作成したプロジェクトは GitHub に上げてあるので、気になる方は手元で動かしてみてください。

登場人物

サンプルプロジェクトに登場するのは以下の3つのファイルです。

  1. Activity (MainActivity.java): 初期化以外何もしない
  2. Animator オブジェクト (DrawerAnimator.java): アニメーションの状態を表すオブジェクト
  3. Layout XML (activity_main.xml): 状態に応じた View の表示を定義するレイアウトファイル

準備: アニメーションの状態を 0 から 1 に正規化する

前準備として、各 View のアニメーションの状態を1つの変数で表すために、状態変数 t を定義します。
この状態変数 t は
0(=ドロアが完全に閉じた状態)
から
1(=ドロアが完全に開いた状態)
に変化するとします。
そうすると、各 View のアニメーションの状態は状態変数 t を用いて以下の式で表すことができます。

1. View A にある ▼ ボタンの回転アニメーション(rotation: 0 → 180 )

rotation = 180 * t

2. View B にかけるグレーマスクのアルファ値のアニメーション(alpha: 0 → 1)

alpha = t

3. View C の拡大・縮小アニメーション(height: 0dp → 200dp)

height = 200dp * t

このように各 View のアニメーション状態を状態変数 t を用いて表現することによって、
アニメーションを制御する際に状態変数 t だけを気にすれば良くなります。
DataBinding を使うことよりも、状態変数 t という1つの変数で表す方が簡略化するポイントかもしれません。

Layout XML に状態を表す式を記述する

ここからが DataBinding の出番です。
状態変数 t を用いた式を Layout XML に記述していきます。

1. View A にある ▼ ボタンの回転アニメーション(rotation: 0 → 180 )

activity_main.xml
<ImageButton
                    android:id="@+id/imageButton"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:background="@null" android:onClick="@{animator::toggleDrawer}"
                    android:padding="16dp"
                    android:rotation="@{180f*animator.t}"  ここに式を記述しています
                    app:srcCompat="@android:drawable/arrow_down_float"/>

2. View B にかけるグレーマスクのアルファ値のアニメーション(alpha: 0 → 1)

activity_main.xml
<FrameLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:alpha="@{animator.t}"  ここに式を記述しています
            android:background="#88000000">

3. View C の拡大・縮小アニメーション(height: 0dp → 200dp)

activity_main.xml
<FrameLayout
                android:layout_width="match_parent"
                android:layout_height="200dp"
                android:background="#BFE9DB"
                app:height="@{animator.t * 200f * animator.density}"  ここに式を記述しています
>

View の高さは回転やアルファとは違い、layout_height に DataBinding を記述することができません。
そのため、カスタムバインディングを用いています。
app:height のバインディングアダプタは以下のように定義しています。

DrawerAnimator.java
@BindingAdapter("height")
    public static void setHeight(View view, float height) {
        ViewGroup.LayoutParams layoutParams = view.getLayoutParams();
        layoutParams.height = (int) height;
        view.setLayoutParams(layoutParams);
    }

これで、Layout XML は 状態 t における View の表示 を定義したものになりました。
続いて、状態変数 t を制御する部分を実装していきます。

Animator オブジェクト で状態変数 t を制御する

ここまでくれば、あとは▼ボタンをタップされた際に、状態変数 t を制御する処理を実装すれば OK です。
以下のソースコードではアニメーション途中に▼ボタンが押された時に不自然にならないように制御する処理を入れていますが、基本的には ValueAnimator で状態変数 t を制御しているだけです。

DrawerAnimator.java
public void toggleDrawer(View imageButton) {
        if (mAnimator != null) {
            mAnimator.cancel();
        }
        float toParam = isOpening ? 0.0f : 1.0f;
        mAnimator = ValueAnimator.ofFloat(mT, toParam);
        mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                float animatedParam = (float) valueAnimator.getAnimatedValue();
                isOpening = mT < animatedParam;
                mT = animatedParam;
                notifyPropertyChanged(BR.t);
            }
        });
        mAnimator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                super.onAnimationEnd(animation);
                mAnimator = null;
            }
        });
        mAnimator.start();
    }

以下の動画のように、▼ボタンを連打した場合でも自然な動きになっています。
renda2.gif

Activity では DataBinding と Animator オブジェクトの初期化をするだけ

状態変数 t における View の表示は Layout XML で定義して、状態変数 t の制御は Animator オブジェクトで定義しました。また、▼ボタンが押されたら Animator の toggleDrawer メソッドを呼ぶ処理も Layout XML で定義されています。
したがって、Activity では DataBiding と Animator オブジェクトの初期化をするだけです。

MainActivity.java
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
        binding.setAnimator(DrawerAnimator.getInstance(getResources().getDisplayMetrics().density));
    }
}

DrawerAnimator がシングルトンで実装されていることを疑問に思った方もいらっしゃるのではないでしょうか? これは、View の階層構造が複雑な時に効果を発揮します。例えば、Activity の内部にある Fragment 内のアニメーションを、親の Activity 側から制御したい時、シングルトンのインスタンスを取得すれば Activity 側から制御することが可能になります。

まとめ

この記事では、複数 View の同時アニメーションを DataBidingin を用いてシンプルに実装する方法を紹介しました。
筆者が開発を担当したアプリが、View 構造が複雑かつアニメーションがリッチということで、どのように実装するか頭を悩ませていました。また、アニメーションは細かい調整が使い勝手を左右するため、プロダクトオーナーとモノを見ながら細かい調整を重ねて行く必要がありました。
今回紹介した方法で実装したことによって、アニメーションの微調整を柔軟に行いやすくなったと感じました。また、アニメーション関連のバグ件数もリニューアル前より抑えられたように感じます。

この記事を書くためにサンプルで作成したプロジェクトはこちらです。気になる方は手元で動かしてみてください!

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

FirebaseのRealtime Databaseでクリアタイムランキングを作成する(Androidアプリ)

前回の続きです。
https://qiita.com/KIRIN3qiita/items/4573d26187b8c9e53fc6

今回はRealtime Databaseの実践編としてゲームクリアタイムのランキングを作成します。
qiita.png

私が昔作成したお釣り支払いゲームのクリアタイムランキング画像です。

自作のしょぼいゲームアプリでも、クリアタイムランキングがあると少しは形になります。(インストール数100ぐらいですが・・・)
至高の支払い(https://play.google.com/store/apps/details?id=jp.kirin3.changegame&hl=ja)
これを応用すればスコアランキングなども作れます。

ルール作成

$ Variablesを利用して、ユーザーごとのデータを保存するルールを作成します。

$ Variablesについて
---------------------------
$ 接頭辞を持つキャプチャ変数を宣言することにより、読み取りや書き込みのパスの一部をキャプチャできます。これはワイルドカードとして機能し、ルール宣言の内部で使用するために該当のキーの値を保存します
---------------------------

ユーザーごとの名前、クリア時間、クリア日時、ユーザーIDを保存するルールを作成しました。

ルール
{
  /* Visit https://firebase.google.com/docs/database/security to learn more about security rules. */
  "rules": {
     "info": {
      ".read": true,
      ".write": true,
      "$user_id": {
        "name": {
          ".validate": "newData.isString() &&
                          newData.val().length < 9"
        },
        "time": {
          ".validate": "newData.isNumber()"
        },
        "date": {
          ".validate": "newData.isString()"
        },
        "user_id": {
          ".validate": "newData.isString()"
        }
      }
    }
  }
}

"$user_id"の項目の中に"user_id"があるのは気持ちが悪いですが、同じ階層のデータを引き抜くイメージなのであると便利です。

データ登録

まずデータ保存、取得に利用するクラスを作成。

User
public static class User {
    public int rankingNo;
    public String name;
    public Double time;
    public String date;
    public String userId;

    // 空のコンストラクタの宣言が必須
    public User() {
    }

    public User(String _name, Double _time,String _date,     String _userId) {
        name = _name;
        time = _time;
        date = _date;
        userId = _userId;
    }
    public void setRankingNo( int _rankingNo ){
        rankingNo = _rankingNo;
    }

    public Integer getRankingNo(){
        return rankingNo;
    }
    public String getName(){
        return name;
    }
    public Double getTime(){
        return time;
    }
    public String getDate(){
        return date;
    }
    public String getUserId(){
        return userId;
    }
}
データ登録
// とりあえずUserIdはUUIDで作成。(その後、毎回変わらないようにプリファランスに保存するなどが必要)
String sUserId = UUID.randomUUID().toString();

User user = new User( "UMEHARA",5.3,"2019-10-21 09:00:27" ,sUserId);

// インスタンスの取得
FirebaseDatabase database = FirebaseDatabase.getInstance();

// ファイルパスを指定してリファレンスを取得
DatabaseReference ref = database.getReference("info");

// データを登録
ref.child(sUserId).setValue(user);

データ取得

取得前に一度だけ終端データを登録しておきます。(なぜ必要かは後で説明)

終端データ登録
public void SaveTerminal(){
    String userId = TARMINAL;
    String userName = "ターミナル";
    Double time = 99999.9;
    String date = "1999-01-01 00:00:00";

    FirebaseDatabase database = FirebaseDatabase.getInstance();
    DatabaseReference ref = database.getReference("info");

    User user = new User( userName,time,date,userId );
    ref.child(userId).setValue(user);
}
データ取得
// ユーザーデータ配列
public static ArrayList<User> sUsers;
// ランキングの最大取得数
final int OUTPUT_RANKING_NUM = 100;

FirebaseDatabase database = FirebaseDatabase.getInstance();
// ファイルパスを指定してリファレンスを取得
final DatabaseReference refUser = database.getReference("info");
// クリア時間でソート、最大取得数を設定
refUser.orderByChild("time").limitToFirst(OUTPUT_RANKING_NUM + 1).addChildEventListener(new ChildEventListener() {
    @Override
    public void onChildAdded(DataSnapshot dataSnapshot, String prevChildKey) {
        User user = dataSnapshot.getValue(User.class);

        // ランキング順位もオブジェクトに保存
        sRankNo++;
        user.setRankingNo(sRankNo);

        // 終端データを受け取ったら、アダプターに渡し、リストビューに表示
        if( dataSnapshot.getKey().equals(TARMINAL) || sRankNo == OUTPUT_RANKING_NUM + 1 ){
            UserAdapter adapter = new UserAdapter(getApplicationContext(), 0, sUsers);
            sListView.setAdapter(adapter);
        }
        // 配列に保存
        else {
            sUsers.add(user);
        }
    }
    @Override
    public void onChildChanged(DataSnapshot dataSnapshot, String s) {
    }
    @Override
    public void onChildRemoved(DataSnapshot dataSnapshot) {
    }
    @Override
    public void onChildMoved(DataSnapshot dataSnapshot, String s) {
    }
    @Override
    public void onCancelled(DatabaseError databaseError) {
    }
});

取得したデータを配列に入れて、アダプターに渡し、ListViewで表示します。
データはクリア時間でソートされ、最大100件の取得に設定しています。
データの取得完了が判断できないので終端データ(必ず最後に取得するデータ)を登録してデータの終わりを判断しています。ルール文で"$user_id"の項目の中に"user_id"があるなど、もっとよい書き方があるかもしれませんが、一応これでも動くということでご参考にして頂けたらと思います。
Realtime Databaseは癖がすごくて使いこなすのは大変ですね。

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

FirebaseのRealtime Databaseの設定から簡単なデータ入力まで(Androidアプリ)

概要

Firebaseのアカウントがあることが前提です。
Firebase Realtime Databaseの公式解説はこちら
簡単に説明すると、リアルタイム更新に特化した簡易データベースで、チャットなど雑多なことに使うことが多いようです。

Realtime Databaseの始め方

Android Studioの上のメニューにあるTools → Firebaseを選択。
Assistantが表示されるのでRealtime Databaseを選択。
1.png

セットアップ方法が書かれたリンクが表示されます。
スタートガイド
これを読まなくても番号付きで手順が書いてあり、
ボタンをクリックしていくだけで、以前は設定できました。
(結果としては今回は苦戦しました・・・)
2.png

Realtime Databaseの設定

①Connected your app to Firebase

[Connected to Firebase]をクリックするとGoogleアカウントの選択画面が表示され、認証を許可すると自動的にFirebaseにプロジェクトを作成、リンクしてくれます。
Firebaseにアクセスすると、プロジェクトが見えるはずです。

②Add the Realtime Database to your app

[Add the Realtime Database to your app]をクリックすると以下を自動で追加してくれます。

build.gradle(project-level)
// Add Firebase Gradle buildscript dependency
classpath 'com.google.gms:google-services:4.0.1'
app/build.gradle
// Add Firebase plugin for Gradle
apply plugin: 'com.google.gms.google-services'

// build.gradle will include these new dependencies:
compile 'com.google.firebase:firebase-database:16.0.1:15.0.0'

この状態でビルドすると、なぜかコンパイルが通らない。
implementation 'com.android.support:appcompat-v7:27.1.1'
の赤線のエラー内容を見ると

----------------------------------
All com.android.support libraries must use the exact same version specification (mixing versions can lead to runtime crashes). Found versions 27.1.1, 26.1.0. Examples include com.android.support:animated-vector-drawable:27.1.1 and com.android.support:support-media-compat:26.1.0 less... (Ctrl+F1)
There are some combinations of libraries, or tools and libraries, that are incompatible, or can lead to bugs. One such incompatibility is compiling with a version of the Android support libraries that is not the latest version (or in particular, a version lower than your targetSdkVersion). Issue id: GradleCompatible

----------------------------------
implementation 'com.android.support:appcompat-v7:27.1.1'

implementation 'com.google.firebase:firebase-database:16.0.1:15.0.0'
の内部で別バージョンの同一ライブラリが見つかったとのこと。

firebase-databaseのほうが古いライブラリを含んでいるので最新を探し入れてみても、同じ結果でした。
(参照)Firebase Android Release Note
Realtime Database com.google.firebase:firebase-database:16.0.3

仕方がなくappcompatのほうを切り替え。
implementation 'com.android.support:appcompat-v7:27.1.1'

implementation 'com.android.support:appcompat-v7:26.1.0'
compileSdkVersionとtargetSdkVersionを26に変更。

そうすると次のエラー
----------------------------------
Could not find firebase-database-15.0.0.jar (com.google.firebase:firebase-database:16.0.1).
----------------------------------
こんなjava Archiveはないとのこと、自動に追加しておいてどういうことかと思いながらも、
スタートガイド
を読むと

app/build.gradle
implementation 'com.google.firebase:firebase-database:16.0.3'

と、最新のにしてとのこと。

そうすると

app/build.gradle
implementation 'com.google.firebase:firebase-core:16.0.4'

も入れてとWarningが出るので従う。
ここら辺の内部ライブラリバージョンが違う問題はもうすぐ改善されるということをGoogleの開発者ブログに書いてあったような。

③Configure Firebase Database Rules

認証についてのリンクとDBの作成ルールのリンクがあります。
Firebase Authentication
データベース ルールを使ってみる

認証については、ユーザーの個人情報など機密性の高いものにする場合に必要です。
簡易的なチャットやゲームランキングなどでは使う必要はないと思います。(たぶん)

では、ブラウザでFirebaseのコンソールにアクセスして、左タブのDatabaseをクリック。
データベースの作成をクリック。
テストモードで作成。

3.png
するとタブがCloud Firestore[ベータ版]になっているので、
Realtime Databaseにしてください。
4.png

とにかくDBのルール作成の癖がすごいので、作成が難しくなっています。
リレーショナルDBのような設計はできません。
細かいルールについては以下のリンクを参照してください。


〇Androidについて
・ガイド
スタートガイド
データベースの構造化
Android でのデータの読み取りと書き込み
Android 上でのデータのリストの操作
Android でのオフライン機能の有効化

・リファレンス
パッケージサマリー

〇セキュリティとルールについて
・ガイド
Firebase Realtime Database ルールについて
データベース ルールを使ってみる
データのセキュリティ保護
ユーザーベースのセキュリティ
非安全性を解決する
データのインデックス作成
REST で Firebase Realtime Database ルールを管理する

・リファレンス
Firebase Database Security Rules API
Firebase Security Rules Regular Expressions
Firebase Security Rules for Cloud Storage Reference


とりあえず簡単なルールを作成

ルール
{
  /* Visit https://firebase.google.com/docs/database/security to learn more about security rules. */
  "rules": {
    "info":{
      "user": {
        ".read": true,
        ".write": true,
        "name": {
          ".validate": "newData.isString()"
        },
        "age": {
          ".validate": "newData.isNumber()"
        }
      }
    }
  }
}

infoの下にuser、その下にname(String型),age(Number型)を作成しただけのルールです。
階層を深くしたのは、パスの指定ルールをわかりやすくするためです。

④Write to your database

早速データを書き込んでみます。
Android でのデータの読み取りと書き込み
がわかりやすいです。
データを個別に登録することも可能ですが、かなり面倒な作りになるので、クラス単位での登録、取得のほうが楽です。

〇クラスの作成
(注意)そのオブジェクトを定義するクラスに、引数を取らないデフォルト コンストラクタと、代入するプロパティのパブリック ゲッターが存在することが条件です。

User
public static class User {
    public String name;
    public Integer age;

    // 空のコンストラクタの宣言が必須
    public User() {
    }

    public User(String _name, Integer _age) {
        name = _name;
        age = _age;
    }
    public String getName(){
        return name;
    }
    public Integer getAge(){
        return age;
    }
}

〇データ登録

register
User user = new User( "山田太郎",30 );
// インスタンスの取得
FirebaseDatabase database = FirebaseDatabase.getInstance();

// ファイルパスを指定してリファレンスを取得
DatabaseReference refName = database.getReference("info/user");
// データを登録
refName.setValue(user);

Firebaseのデータを見てみると
5.png

しっかり登録されてますね。

⑤Read from your database

データの読み取りを行います。
Android でのデータの読み取りと書き込み
よりも
ガイド→管理→データを取得する
のほうがわかりやすいかもしれません。
ドキュメントの例文がとっ散らかっていてわかりづらい・・・

簡単に書くとイベントリスナーをセットすることで、全取得や更新取得などができるということです。
とりあえず全取得だけ記載。

〇データ取得

reader
// インスタンスの取得
FirebaseDatabase database = FirebaseDatabase.getInstance();
// ファイルパスを指定してリファレンスを取得
final DatabaseReference refUser = database.getReference("info");
refUser.addChildEventListener(new ChildEventListener() {
    @Override
    public void onChildAdded(DataSnapshot dataSnapshot, String prevChildKey) {

        User user = dataSnapshot.getValue(User.class);
        Log.w( "DEBUG_DATA", "user.name = " + user.name);
        Log.w( "DEBUG_DATA", "user.age = " + user.age);
    }

    @Override
    public void onChildChanged(DataSnapshot dataSnapshot, String s) {
    }

    @Override
    public void onChildRemoved(DataSnapshot dataSnapshot) {
    }

    @Override
    public void onChildMoved(DataSnapshot dataSnapshot, String s) {
    }

    @Override
    public void onCancelled(DatabaseError databaseError) {
    }
});

⑥Optional: Configure ProGuard

プロガードを設定する場合は気を付けてねって内容。
使ってないので割愛・・・

When using Firebase Realtime Database in your app along with ProGuard you need to consider how your model objects will be serialized and deserialized after obfuscation. If you use DataSnapshot.getValue(Class) or DatabaseReference.setValue(Object) to read and write data you will need to add rules to the proguard-rules.pro file:

-----
# Add this global rule
-keepattributes Signature

# This rule will properly ProGuard all the model classes in
# the package com.yourcompany.models. Modify to fit the structure
# of your app.
-keepclassmembers class com.yourcompany.models.** {
*;
}
-----

⑦Preapre for Launch

チェックリストでチェックしてねという内容。
大したことはない。

⑧Next Steps

複数のデータベースを利用することもできますよ、という内容。
複数のデータベースでスケールする
メルカリは相当な数のデータベースを結び付けて膨大な量のチャットを実現していると聞いたことがあるような。
本当にサーバーレスな時代ですね。

基本編だけで長くなってしまいました。
このままではただ単に簡単なデータを入力、取得しただけになっていて、使い道がなさそうです。
応用編は後程。

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

Android キーボードを隠す方法

Andriod キーボードを隠す方法

今回はAndrod開発においてキーボードをJavaコードで隠す方法を記載します。

簡単なのでもうコード書きますね笑

 public static void hideKeyboard(Activity activity) {
        InputMethodManager imm = (InputMethodManager) activity.getSystemService(Activity.INPUT_METHOD_SERVICE);
        View view = activity.getCurrentFocus();
        if (view == null) {
            view = new View(activity);
        }
        imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
    }

これをUtilsクラスみたいなところに記載して、引数にActivityを渡せば完了です。
今回は登録画面などでフォーカスが離れた時にキーボードを隠す必要があったので、実装しました。

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

osmdroid を使って Android の OpenStreetMap に マーカーを表示する

osmdroid を使って OpenStreetMap を Android に表示する
の続きです

MainActivity.java

タイトル, 説明, 緯度, 経度からマーカーのリストを作る

    List<OverlayItem> items = new ArrayList<>();

            items.add(new OverlayItem(タイトル, 説明, new GeoPoint(緯度, 経度)));

マーカーをクリックした時の処理

            mMyLocationOverlay = new ItemizedIconOverlay<>(items,
                    new ItemizedIconOverlay.OnItemGestureListener<OverlayItem>() {
                        @Override
                        public boolean onItemSingleTapUp(final int index, final OverlayItem item) {
     // Toast を表示する
                    Toast.makeText(this, item.getTitle(), Toast.LENGTH_LONG).show();
                            return true; // We 'handled' this event.
                        }

                        @Override
                        public boolean onItemLongPress(final int index, final OverlayItem item) {
     // Toast を表示する
                    Toast.makeText(this, item.getTitle(), Toast.LENGTH_LONG).show();
                            return false;
                        }
                    }, getApplicationContext());
            this.mMapView.getOverlays().add(this.mMyLocationOverlay);
        }

地図にマーカーを重ねる

            final MinimapOverlay miniMapOverlay = new MinimapOverlay(this,
            mMapView.getTileRequestCompleteHandler());
            mMapView.getOverlays().add(miniMapOverlay);

以上でマーカーが表示される
markers.png

github にソースを公開した

https://github.com/ohwada/Android_Samples/tree/master/Osmdroid2

ひと工夫しています。
csv ファイルからマーカーのリストを生成するようにした。

csv ファイルの例

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

mdroid を使って OpenStreetMap に マーカーを表示する

osmdroid を使って OpenStreetMap を Android に表示する
の続きです

MainActivity.java

タイトル, 説明, 緯度, 経度からマーカーのリストを作る

    List<OverlayItem> items = new ArrayList<>();

            items.add(new OverlayItem(タイトル, 説明, new GeoPoint(緯度, 経度)));

マーカーをクリックした時の処理

            mMyLocationOverlay = new ItemizedIconOverlay<>(items,
                    new ItemizedIconOverlay.OnItemGestureListener<OverlayItem>() {
                        @Override
                        public boolean onItemSingleTapUp(final int index, final OverlayItem item) {
     // Toast を表示する
                    Toast.makeText(this, item.getTitle(), Toast.LENGTH_LONG).show();
                            return true; // We 'handled' this event.
                        }

                        @Override
                        public boolean onItemLongPress(final int index, final OverlayItem item) {
     // Toast を表示する
                    Toast.makeText(this, item.getTitle(), Toast.LENGTH_LONG).show();
                            return false;
                        }
                    }, getApplicationContext());
            this.mMapView.getOverlays().add(this.mMyLocationOverlay);
        }

地図にマーカーを重ねる

            final MinimapOverlay miniMapOverlay = new MinimapOverlay(this,
            mMapView.getTileRequestCompleteHandler());
            mMapView.getOverlays().add(miniMapOverlay);
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

osmdroid を使って OpenStreetMap を Android に表示する

Open Street Map(OSM)

自由な地図をみんなで作るプロジェクトです。
「地図のWikipedia」とも言われる。
GoogleMap との違いは、宗教論争になるので、割愛。

https://openstreetmap.jp/

 osmdroid

OpenStreetMap を Android に表示するためのライブラリ。

https://github.com/osmdroid/osmdroid

Google Play にデモアプリが公開されている。

https://play.google.com/store/apps/details?id=org.osmdroid

簡単なアプリを作る

Android Studio で、空のプロジェクトを作る

osmdroid ライブラリを取り込む。

build.gradle

allprojects {
    repositories {
        jcenter()
      mavenCentral() // 追加する
    }

app/build.gradle

dependencies {

    compile 'org.osmdroid:osmdroid-android:6.0.3' // 追加する

AndroidManifestに permission を追加する

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

activity_main.xml に MapView を追加する

      <org.osmdroid.views.MapView android:id="@+id/mapView"
                android:layout_width="fill_parent" 
                android:layout_height="fill_parent" />

MainAvtivity

public class MainActivity extends Activity {

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

       // 注意 setContentView より前に置く
       Configuration.getInstance().load(getApplicationContext(),               PreferenceManager.getDefaultSharedPreferences(getApplicationContext()));

         setContentView(R.layout.activity_main);

以上で地図を表示するが、ズームの初期値が0なので、
小さな世界地図が表示される。
zoom0.png

中心の緯度経度とズームを設定する。

    mMapView = (MapView) findViewById(R.id.mapView);

       IMapController mapController =  mMapView.getController();
         mapController.setZoom( ズームレベル );
          GeoPoint centerPoint = new GeoPoint( 緯度, 経度 );
          mapController.setCenter(centerPoint);

タッチでズームできるようにする

     mMapView.setBuiltInZoomControls(true);
     mMapView.setMultiTouchControls(true);

以上で地図を表示する
map.png

onResume と onPause の処理を追加

なくとも動くようだが、流儀のようなので。

    public void onResume(){
        super.onResume();
        if (mMapView!=null) {
            mMapView.onResume();
        }
    }

    public void onPause(){
        super.onPause();
        if (mMapView!=null) {
            mMapView.onPause();
        }
    }

サンプルコードを github に公開した。

https://github.com/ohwada/Android_Samples/tree/master/Osmdroid1

osmdroid を使った感想

  • GoogleMap API と似た API であり、違和感なく使えた。

  • GoogleMap API と異なり、API Key が不要なので、
    気軽に試せて、嬉しい。

  • 新しいライブラリなので、解説記事が少ないのが、難点。

関連記事

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

Androidアプリをコマンドラインからビルドする

「Android Studio、結構メモリを食うので、貧弱なパソコンでアプリを開発するのは無理。」
そんな時代、もう終わりです。

アプリはコマンドラインからでもビルドすることができます。

Gradleの便利さ

以前も、コマンドラインからビルドすることはできましたが、Gradleを採用していなかったため
いろいろなコマンドを実行し、コンパイル、ビルド、APKの生成・署名を行う必要があって
非常にめんどくさい処理でした。

そして、Googleがリリースした新しいIDE「Android Studio」では、新しいビルドシステムが導入されて効率がアップしました。
その恩恵はコマンドラインでも受けることができます。

ビルドは「gradle assembleXXX」!

いきなりの見出しでびっくりしたかもしれませんが、これだけでビルドできます。
Gradleは、Gradle公式サイトを参考に導入します。
Windowsでも、Macでも、Linuxでもコマンド操作は基本的に同じです。

まずコンソールを起動し、ビルドしたいプロジェクトのフォルダへ行きます。

そこで試しに

gradle

と一度入力して実行してみます。
問題なければ

> Task :help

Welcome to Gradle 4.10.2.

To run a build, run gradle <task> ...

To see a list of available tasks, run gradle tasks

To see a list of command-line options, run gradle --help

To see more detail about a task, run gradle help --task <task>

For troubleshooting, visit https://help.gradle.org

BUILD SUCCESSFUL in 1m 16s
1 actionable task: 1 executed

と出力して終了します。
もし、SDKのディレクトリがおかしいというエラーが出た場合はlocal.propertiesの、sdk.dirを確認します。

ビルドタイプ:リリース

リリース用にAPKを生成する場合はgradle assembleReleaseを実行します。

ビルドタイプ:デバッグ

デバッグ用にAPKを生成する場合はgradle assembleDebugを実行します。

その他タスク

その他、デバイスにAPKをインストールするタスクもあります。
詳細はgradle tasksgradle tasks --allを実行します。

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

AACのViewModelをFactoryクラス不要で簡単に生成できる拡張関数

Androidで、MVVM及びdatabindingを行う場合、Android Architecture ComponentsのViewModelを使うことが多いと思います。
このViewModelを生成する際、コンストラクタに引数が必要だとFactoryクラスを作成する必要がありますが、ViewModelが多くなってくるとその分Factoryも増えていき、作成・管理が面倒になったので拡張関数を作成しました。

結論

忙しい人のために先に結論
Fragment.getViewModelFragment.viewModelProviderを作りました。

使い方

class SampleFragment: Fragment(), SampleNavigator {
    // フィールドでvalで持ちたい場合は、viewModelProvider
    private val viewModel by viewModelProvider {
        val str = requireNotNull(arguments?.getString(ARG_SAMPLE_STR))
        SampleViewModel(str, this)
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val str= requireNotNull(arguments?.getString(ARG_SAMPLE_STR))
        // 一時的に使用したい(or lateinit varで初期化したい)場合はgetViewModel
        val vm = getViewModel { SampleViewModel(str, this) }

        // ActivityのViewModelを参照したい場合
        val viewModelOfActivity = requireActivity().getViewModel{ SampleActivityViewModel() }
    }

    // 本質外
    companion object {
        private const val ARG_SAMPLE_STR = "ARG_SAMPLE_STR"

        fun newInstance(str: String) = SampleFragment().apply {
            arguments = bundleOf(ARG_SAMPLE_STR to str)
        }
    }
}

for Fragment

FragmentExt.kt
inline fun <reified VM : ViewModel> Fragment.getViewModel(crossinline factory: () -> VM): VM {
    return ViewModelProviders.of(this, object : ViewModelProvider.Factory {
        override fun <VM : ViewModel> create(modelClass: Class<VM>): VM {
            return requireNotNull(modelClass.cast(factory()))
        }
    }).get(VM::class.java)
}

inline fun <reified VM : ViewModel> Fragment.viewModelProvider(crossinline factory: () -> VM) = lazy {
    getViewModel { factory() }
}

for FragmentActivity

上の拡張関数で生やす元がFragmentActivityになっただけ

FragmentActivityExt.kt
inline fun <reified VM : ViewModel> FragmentActivity.getViewModel(crossinline factory: () -> VM): VM {
    return ViewModelProviders.of(this, object : ViewModelProvider.Factory {
        override fun <VM : ViewModel> create(modelClass: Class<VM>): VM {
            return requireNotNull(modelClass.cast(factory()))
        }
    }).get(VM::class.java)
}

inline fun <reified VM : ViewModel> FragmentActivity.viewModelProvider(crossinline factory: () -> VM) = lazy {
    getViewModel { factory() }
}

解説

Fragment(とFragmentActivity)に拡張関数を生やして、ViewModelProviders.of().get()をいい感じにしてくれればゴールです。
ViewModelProviders.ofに必要なのは

  • Fragment
  • ViewModelProvider.Factory クラスをもらってViewModelを返すもの
  • get(ViewModelのクラス)

Fragmentはthisが使えます。
ViewModelProvider.Factoryの中身は、createを実装するもので、これはViewModelを生成して返せばゴールです。
Factoryは、引数を拡張関数は知らなくて良い(外に任せる)ので、引数無しでVMを返す高階関数で表現できます。
factory: () -> VM
getにはクラスを入れる必要があります。
拡張関数の引数でもらってきて入れるのが簡単ですが、VMはViewModelを継承したクラスと分かっているので、VM::class.javaができればクラスを引数に取る必要はありません。
そこで reifiedの登場です。

reified

3行でまとめると

reified type paramter = 具象化された型パラメータ
→reifiedを使うことで、T::class.javaを実現できる
→reifiedを使うには、inline関数である必要がある

公式サイトや太郎さんの記事が参考になります。

公式サイト
日本語
太郎さんのサイト

reifiedを使えば良いですが、このためにはinline関数にする必要があります。
ただ、crossinlineをつけないとfactory: () -> VMがcreate内から参照できません。

crossinline

まとめると

inline関数では、渡されてラムダを関数本体から直接ではなく、
ローカルオブジェクトやネストされた関数などの別の実行コンテキストから呼び出すことは許可されていない
→ラムダパラメータにcrossinlineをつけることで可能になる

詳しくは公式サイトのreifiedの上に載っています。

requireNotNull(modelClass.cast(factory()))
は警告回避のために行っています。
as TのUNCHECKED_CASTの回避のために modelClass.cast(factory())
NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONSの回避のために requireNotNull

以上を組み合わせることで拡張関数を実現しています。
調べていたとき、reifiedが便利すぎて感動しました。
T::class.javaが出来るなんてジェネリクス好き歓喜。

道行き

結論に至るまでの道のりを備忘録がてら記載。

ViewModelProvidersの利用

通常、AACのViewModelを取得するためには、ViewModelProviders.ofを利用します。

// 引数がない場合
ViewModelProviders.of(Fragment).get(SampleViewModel::class.java)
// 引数が必要な場合
ViewModelProviders.of(Fragment, SampleViewModelFactory(arg1, arg2)).get(SampleViewModel::class.java)

多くの場合、引数が必要な場合はViewModelクラスと対になるFactoryClassを作ります。

SampleViewModelFactory.kt
class SampleViewModelFactory(arg1: String, arg2: Int) : ViewModelProvider.Factory {

    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
        if (modelClass != SampleViewModel.class.java) {
            throw IllegalArgumentException("ViewModel class is not SampleViewModel")
        }
        return TextToSpeechDemoViewModel(arg1, arg2) as T
    }
}

Util化

毎回クラスを作るのは面倒です。
引数を見てみると、必要なのはViewModelProder.Factoryとなっています。
Factoryクラスを作るときに、実装しましたね。要はこれがあればよいのです。
image.png
リファレンス

Factoryの中にはcreateメソッドがあり、中を見てみるとクラスをもらって引数を利用してViewModelを生成し、Tにキャストして返しています。
ViewModelによって必要な引数はバラバラです。
なのでViewModelを生成する処理は引数にとり外に任せることにしましょう。
ViewModelを生成する処理を高階関数 () -> Tとして引数に渡せば、生成ロジックを外に任せることができます。
これでUtilクラスで汎用的にできそうです。

ViewModelProvidersUtils.kt
// 引数がない場合の生成メソッド
fun <T : ViewModel> create(fragment: Fragment, clazz: Class<out T>): T {
    return ViewModelProviders.of(fragment, object : ViewModelProvider.Factory {
        override fun <T : ViewModel> create(modelClass: Class<T>): T {
            return clazz.newInstance() as T
        }
    }).get(clazz)
}


// 引数がある場合の生成メソッド
fun <T : ViewModel> create(fragment: Fragment, clazz: Class<out T>, factory: () -> T): T {
    return ViewModelProviders.of(fragment, object : ViewModelProvider.Factory {
        override fun <T : ViewModel> create(modelClass: Class<T>): T {
            return factory() as T
        }
    }).get(clazz)
}

使う側

// 引数なし
val vm = ViewModelProvidersUtils.createViewModel(SampleViewModel::class.java)
// 引数あり
val vm = ViewModelProvidersUtils.createViewModel(SampleViewModel::class.java) {
    SampleViewModel(arg1, arg2)
}

Utilの2つのメソッドの実装をよく見ると、ほとんど同じ処理をしています。
引数一つの createViewModelは、引数二つの createViewModelfactory() as Tに当たる部分でclazz.newInstance()をしていますね。
kotlinはデフォルト引数が使えるので一つにできます。

ViewModelProvidersUtils.kt
fun <T : ViewModel> create(fragment: Fragment, clazz: Class<out T>, factory: () -> T = { clazz.newInstance() as T }): T {
    return ViewModelProviders.of(fragment, object : ViewModelProvider.Factory {
        override fun <T : ViewModel> create(modelClass: Class<T>): T {
            return factory() as T
        }
    }).get(clazz)
}

これで、
第二引数を指定しない場合はclazz.newInstance()が行われる
指定すれば引数ありの場合に対応できる
ようになりました。

拡張関数化

必ずFragmentから呼ぶもので、汎用的に使えそうなので拡張関数にしてみましょう。

FragmentExt.kt
fun <T : ViewModel> Fragment.createViewModel(clazz: Class<out T>, factory: () -> T = { clazz.newInstance() as T }): T {
    return ViewModelProviders.of(this, object : ViewModelProvider.Factory {
        override fun <T : ViewModel> create(modelClass: Class<T>): T {
            return factory() as T
        }
    }).get(clazz)
}

使う側

Fragment内からの呼び出し
// 引数なし
val vm = createViewModel(SampleViewModel::class.java)
// 引数あり
val vm = createViewModel(SampleViewModel::class.java) {
    SampleViewModel(arg1, arg2)
}

ここで拡張関数を見てみると、(人間的には)返す型がTと決まっているので、クラスを引数に渡すのは無駄じゃない?と思います。
.get(clazz)のところをT::class.javaとできれば、clazz: Classを無くせそうです。
そこで、reifiedが登場し、最終形になります。
解説へと続くわけです

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