20190327のAndroidに関する記事は7件です。

【Android】ListPopupWindowを使った入力候補の表示

0.実行例

スクリーンショット 2019-03-27 19.13.33.png

1.コード全体

MainActivity.java
package com.example.listpopupwindowtest;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.EditText;
import android.widget.ListPopupWindow;

public class MainActivity extends AppCompatActivity {

    String[] strList = {"あいうえお","かきくけこ","さしすせそ"};

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        final EditText et_1 = (EditText)findViewById(R.id.et_1);
        final ListPopupWindow lpw = new ListPopupWindow(this);
        lpw.setAdapter(new ArrayAdapter<String>(
                this,android.R.layout.simple_list_item_1,strList));
        lpw.setAnchorView(et_1);
        lpw.setModal(true);
        et_1.setOnTouchListener(new View.OnTouchListener() {
            final int DRAWABLE_RIGHT = 2;
            @Override
            public boolean onTouch(View v, MotionEvent event) {
                if(event.getAction()==MotionEvent.ACTION_UP){
                    if(event.getX()>=v.getWidth()-((EditText) v)
                            .getCompoundDrawables()[DRAWABLE_RIGHT].getBounds().width()){
                        lpw.show();
                        ((InputMethodManager)getSystemService(INPUT_METHOD_SERVICE))
                                .hideSoftInputFromWindow(v.getWindowToken(),InputMethodManager.HIDE_NOT_ALWAYS);
                        return true;
                    }
                }
                return false;
            }
        });
        lpw.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                et_1.setText(strList[position]);
                lpw.dismiss();
            }
        });
    }
}

2.入力欄の右端に▼マークを付ける

下記のリソースファイルをダウンロードし、core/res/res/drawable-hdpi/numberpicker_down_normal_holo_light.pngをdrawableフォルダにコピーする。
https://android.googlesource.com/platform/frameworks/base/+/efd1c67

EditTextのdrawableRightで、コピーした画像ファイルを指定する。

    <EditText
        android:id="@+id/et_1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:drawableRight="@drawable/numberpicker_down_normal_holo_light"/>

参考

EditText with a Popup List

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

Android開発はFlutterでやる方がいい説

はじめに

クラスプラットフォームとして語られるFlutterですが、実は、「Android開発だけでもFlutterでやった方がよくね?」 となんとなく思い始めています。
「FlutterってGoogleがAndroid開発を再定義した画期的なものになるんじゃないか」と。

自分は、おっさんなので古い話をしますが、Java開発でEJB2が存在していた頃です。まだ、バージョンが1にもなっていないSpring Frameworkを使った案件にたまたま参加したときの衝撃と同じなんです。「何これ? めっちゃわかりやすい。標準のEJBなんて駄目じゃん。」
今ではEJBは廃れ、Springがデファクトスタンダードになっていますよね。

ただ、使ったことがない人に伝えるのは本当に難しく、納得できない人も多いはずです。
自分でもなんでそう思うのかうまく伝えられる気もしないのですが、言語化してみます。
自分は本気で思っていますが、話半分に読んでください。

ライフサイクルが単純

Android開発で誰もが感じるだろう、これじゃない感。そう、理解しがたいライフサイクル。
「メモリ不足して、Activityが再生成される」とか、「画面の向きを変えたら再生成される」とかとか。
さらにFlagmentが絡むとクラッシュしたりと複雑度は増します。

初期の頃はすごく苦労しましたね。(今もですけど。)
MVPにしたり、MVVMにしたり、Daggerを使ったり、RxJavaを使ったりと色々やりますが、結局この部分は複雑なままです。

これ、Flutterでは気にすることないんです。
Flutter独自のライフサイクルは、Androidと比べると極めて単純です。iosと同等な感覚です。
Android Architecture ComponentsのLifeCycleObserverみたいなものを作成するだけです。

例を出すと以下のクラスを作って、

class LifecycleEventHandler extends WidgetsBindingObserver {
  final Future<void> Function() inactiveCallBack;
  final Future<void> Function() pauseCallBack;
  final Future<void> Function() resumeCallBack;

  LifecycleEventHandler({this.inactiveCallBack, this.pauseCallBack, this.resumeCallBack});

  @override
  Future<Null> didChangeAppLifecycleState(AppLifecycleState state) async {
    switch (state) {
      case AppLifecycleState.inactive:
        if (inactiveCallBack != null) {
          await inactiveCallBack();
        }
        break;
      case AppLifecycleState.paused:
        if (pauseCallBack != null) {
          await pauseCallBack();
        }
        break;
      case AppLifecycleState.suspending:
        break;
      case AppLifecycleState.resumed:
        if (resumeCallBack != null) {
          await resumeCallBack();
        }
        break;
    }
  }
}

以下のようにWidgetで呼び出せば、画面ごとのonPause,onResume相当のことを検知できます。
(Widgetとは、AndroidでいうActivityみたいなものと思ってください。全然違うけど。)

class _SampleState extends State<_SampleWidget> {
  LifecycleEventHandler _eventHandler;
  @override
  void initState() {
    super.initState();
    _eventHandler = LifecycleEventHandler(pauseCallBack: () {
      // onPauseの処理
    });
    WidgetsBinding.instance.addObserver(_eventHandler);
  }
  @override
  void dispose() {
    WidgetsBinding.instance.removeObserver(_eventHandler);
    super.dispose();
  }

また、上記のように initState() と dispose() という単純なライフサイクルをまずは理解していれば良いですし、もっとちゃんとライフサイクルを理解しようとしても簡単に理解できるはずです。

もちろん、開発者オプションの「アクティビティを保持しない」をONにしても普通に動作します。

念の為、ライフサイクル複雑的な記事も示しておきます。
https://academy.realm.io/jp/posts/sf-fabien-davos-modern-android-ditching-activities-fragments/
https://ninjinkun.hatenablog.com/entry/2014/10/16/234611
https://qiita.com/yuya_presto/items/331301cb91bec335ecdf

ホットリロードがめっちゃ早い

AndroidにもInstant Runがありますが全然違います。
爆速とはほど遠く、数分待たされることもあるし、状態が保持されなかったりして、バグなのかが判断できない場合が多々あるので、OFFにしている人も多いでしょう。

FlutterのHot Reloadは真に爆速です。1秒以内です。状態もなくなることもありません。
開発のほとんどの期間をHot Reloadで開発できます。
例外として、最初のWidgetでThemeを設定するので、共通のThemeを変更したい時はrestartする必要があります。がこれも早い。Hot Restartと呼んでいるようです。これは数秒です。

Instant RunとHot Reloadの違いは以下がわかりやすいです。
https://stackoverflow.com/questions/52050660/difference-between-androids-instant-run-vs-flutters-hot-reload-and-react-nativ

標準でMaterialDesignをサポート

Androidのデザインは、ほとんどMaterialDesignにするでしょう。

Flutterでは、MaterialDesignのWidgetが用意されています。Android4.4も同様の見た目になります。
Material Colorも標準で用意されています。その中から選択するだけです。
当然、自由に色をつけることも可能です。

Iconもあります。(ちなみに標準ではないですがfont awesomeなIconもあります。)

MaterialDesignをきちんと知っている必要がありますが、何も考えずに余計なことをしなければMaterialDesignになります。

Themeも用意されていて、これだけを変更すれば全ての画面で変更されます。
カスタマイズも可能です。
(Themeに関しての詳しい記事はこちらを参考してください。)

良くも悪くもDart言語

自分、Kotlinが大好きです。Dartよりも圧倒的に好きです。
「FlutterもKotlinで書ければいいのに。」と思っています。

言語の印象は、Java < Dart < C# < Kotlin みたいに感じています。

ただし、これだけは言いたい。Dart言語の文法は、ほとんどJavaです。JavaScriptに近いかもしれませんが、Javaを知っている人の学習コストは限りなくゼロに近いでしょう。

特にAndroid Javaに比べたら圧倒的に良いです。以下の仕様があります。

  • async,await,Promise(Futureクラス)が標準で用意されていて、非同期処理が簡潔にかける(コールバック地獄にならない)
  • StreamというRxの簡易版が標準で用意されていて、変更対象の検知を簡単にできる
  • 静的型付けで、型推論ができるので、Javaのようにいちいち型を記述する必要がない
  • 言語仕様でFactoryパターンが用意されている。(factory constructor)
  • 流れるようなインターフェイスな言語仕様がある。(..)ドットを二回書くとメソッドを実行しつつインスタンスを返すことができる。Kotlinのapplyよりも簡潔に書ける。(Cascade notationという仕様です)
  • 一応、コレクション系にmapやreduceなど関数型なメソッドは用意されている。ただfirstWhereなど若干使いづらい。(filterじゃないのがC#っぽい)
  • 名前付き引数やデフォルト引数ができる
  • Kotlinのようにnullの場合でも(.?)を使えば良いのでnull分岐が減る(safe navigation operatorもしくはNull-aware operatorsと呼ぶらしい。コメント欄に例を記述しています。)

もしJavaしか知らない人でも上記のキーワードを検索すれば、他の言語を学ぶよりも簡単に理解できると思います。
多分、上記の仕様が理解できたら、もうDart技術者です。

Kotlinと比べて、いまいちな点は、文末のセミコロンと拡張関数がないこととNull Safetyではない点です。
文末のセミコロン、これは諦めるしかないでしょう。

Null Safetyに関しては代替手段として、DDDのValueObjectパターンを使ってnullにならないようにしたり、NullObjectを作ったりでしょうか。

例えばこんな感じのValueObjectを作って、

class UserId {
  final String _value;
  String get value => this._value ?? "";

  const UserId(this._value);

  @override
  bool operator ==(other) => other is UserId && value == other.value;

  @override
  int get hashCode => value.hashCode;

  @override
  String toString() => value;

  bool get isEmpty => this == null || this.value.isEmpty;
}

nullが帰ってくる可能性のあるところで使い、プログラムではこの型で使い続ければ良いでしょう。

var userId = UserId(json["user_id"] as String),

拡張関数の代替手段としては、コンポジットにするぐらいでしょうか。全く、代替にはなってないですが。。
(拡張関数なら、EmojiクラスなどのValueObjectを作った方がいいと思う)

class StringEx {
  final String string;

  StringEx(this.string);

  bool isEmoji(){
    // string変数を使ってゴニョゴニョする
  }

Dartを敬遠して、Flutterを使わないのは非常に勿体ないです。
Dartでつまづくことは限りなくゼロですし、もう一度言いますが、Javaで開発するよりも遥かに良いです。

画面もDartで記述

Androidの場合、XMLで画面を作ったりしますが、Flutterでは全てDartです。
これをマイナスだと思う人もいるかもしれませんが、むしろいいんです。
コードで書くことで、型安全にもなるし、シンプルにやりたいことが実現できます。
巨大なWidgetになってもコードなので、簡単に分割リファクタできます。

KotlinのAnkoのメリットと同じなんでしょうか。(いまいち流行ってない?)
おそらく、それ以上にFlutterのはAndroid Studioのおかげで使いやすくなっていると思われます。

例えば、以下の画像の赤枠ですが、コメントを書いているわけではなく、対応するカッコをAndroidStudioが表示してくれているものです。
スクリーンショット 2019-03-27 14.15.39.png

さらにAndroidStudioがAlt+Enterで候補を表示してくれます。ネストが深くても簡単に削除したり、追加したりできるのです。
スクリーンショット 2019-03-27 14.17.53.png

これらの機能のおかげで、XMLで記述していた時と同程度な可読性だと感じます。

普通のAndroid開発の場合、XMLをPreviewで見れます。初期値をプログラムで設定する場合でも、tools:text="サンプル" みたいに書いておけば、ちゃんとしたデザインで見れるあれです。
残念ながら、Flutterではそれはできません。

ただし、それで不便を感じたことはありません。おそらくHot Reloadが早いからなのでしょう。
いちいちXMLで偽のプレビューを見るのと同じスピードで実際の画面が見れるのです。

いざとなればAndroidネイティブだけの画面に遷移できる

これができるので安心なんです。

最初の画面はできないですが、遷移先の全ての画面をKotlin/Javaで開発できます。

こんな感じです。
MainActivityでdartの処理を受け付けます。
この場合は、NextActivityに遷移します。
NextActivity以降は普通のAndroid開発と同じです。

MainActivity.java
class MainActivity() : FlutterActivity() {

    companion object {
        const val CHANNEL = "sample.ko2ic/toPlattformScreen"
    }
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        GeneratedPluginRegistrant.registerWith(this)

        MethodChannel(flutterView, CHANNEL).setMethodCallHandler(
            object : MethodCallHandler {
                override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
                    if (call.method.equals("toPlattformScreen")) {
                        startActivity(NextActivity.intent(this@MainActivity))
                    } else {
                        result.notImplemented()
                    }
                }
            })
    }
}

dart側では、button押下などで、_toPlattformScreen()を呼ぶだけです。

  static const MethodChannel channel =
      const MethodChannel('sample.ko2ic/toPlattformScreen');

  _toPlattformScreen() {
    try {
      channel.invokeMethod('toPlattformScreen');
    } on PlatformException catch (e) {
      // TODO
    }
  }

ただし、言いたいことは、一年間業務で開発してきましたが、これをせざるを得ない場合がなかったです
ただ、保険として、これができるのは良いことでしょう。

Androidネイティブな機能のアクセスも簡単

ほとんどPluginで実現できますが、足りない機能を使わないと行けない場合はもちろんあるでしょう。
その場合でも恐る必要はありません。
Android開発をバリバリやっている人にとっては、Flutter上でAndroidネイティブな機能にアクセスすることは簡単です。
上記のMainActivityに書いてあることが基本で、あとは、引数や戻り値などを考えれば良いだけです。

実際に自分も業務で利用するGoogle Ad Manger(旧DFP)のPluginなど複数のPluginを自作しましたが簡単でした。

自作のflutter_google_ad_managerもそうですが、PlatformViewという機能で、Flutterの画面の一部分だけAndroidネイティブで表示もできます。

Pluginが簡単に使える

例えば、文字列を共有する場合です。

Androidの場合はこうです。

    Intent shareIntent = new Intent();
    shareIntent.setAction(Intent.ACTION_SEND);
    shareIntent.putExtra(Intent.EXTRA_TEXT, "ほげ");
    shareIntent.setType("text/plain");
    Intent chooserIntent = Intent.createChooser(shareIntent, null);
    activity.startActivity(chooserIntent);

これ、Android開発になれている人にとっては、当たり前だろうけど、知らない人から見ると「Intentって?」、「ACTION_SENDをやるのか」、「createChooserを使うのか」とか、意外と知らないとできないことですね。
これぐらいだったら、ネットですぐにサンプルを見つけられますが、もっと難しい処理だったら学習コストはそれなりに高いと思います。

ところが、Flutterの場合はこれだけです。

Share.share('ほげ');

当たり前ですけど、ライブラリでWrapされているので簡単に記述できます。

まとめ

Androidエンジニアを育てるのは結構大変だと思うんですね。大変にさせているのはライフサイクルに尽きるのかなと。
それ以外にも覚えることは一杯あります。
Flutterでも覚えることはたくさんあります。
ただし、Androidの学習コストとFlutterの学習コストは、Androidの方が高いと思うんです。

ほとんどのチームで一人ぐらいは、Android開発に精通していると思うんです。
そういう人がOSSには存在しない機能を作っていけば、その他の精通していない人はすごく簡単に実装できるようになります。
(もちろん、いずれは開発している人全員が、Androidに精通してもらうのが理想だけど、初期段階では全員が知ってる必要もないのかなと)

精通していない人でもAndroid開発はもちろんできますが、ライフサイクルの複雑さのため徹底したレビューが必要になるし、思わぬクラッシュが出やすいです。

Flutter自体の学習コストですが、普通に作る分には低いと感じます。
画面レイアウトの仕方を覚える必要がありますが、Androidでのレイアウトの仕方と学習コストは変わらないか、それよりも低いかもしれません。

中級ぐらいでわかってないと困ることは、以下の記事に書いてあることとプラスアルファ(例えば、Builderは冪等であるべきとか)ぐらいで大丈夫なんじゃないかな。これらを読むと難しく感じるかもしれないけど、逆に言うとこれぐらいなんです。Flutterの難しいところ。

ちゃんと伝わってるかは自信はないですが、なんとなく、「Android開発だけでもFlutterでやる方がいい」と思った理由を書けたんじゃないかな。

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

Android 「エラー: LinkedHashMapのEntryはpublicではありません。パッケージ外からはアクセスできません」

何かのライブラリのバージョンを上げた際に以下エラーが出てしまいまして、

Uncaught translation error: com.android.dx.cf.code.SimException: invalid opcode ba (invokedynamic requires --min-sdk-version >= 26)

こちらによると、
https://stackoverflow.com/questions/45862140/invokedynamic-requires-min-sdk-version-26
以下を追加すればよいみたいでした。

android {
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }

しかし、これを追加した際に、以下部分で件名のエラーがでているようでした。

    LinkedHashMap<String, String> rokuyoMap = new LinkedHashMap<String, String>(){
        @Override
        protected boolean removeEldestEntry(Entry<String, String> eldest) {
            return size() > 5;
        }
    };

Map.Entry と明示的に?書くことでこのエラーは解消されました。
LinkedHashMapの中のEntryの記述を見ても、Map.Entryとなっていたので、こう書くのが普通なのでしょうかね。
今まで、なんでエラーになっていなかったか不思議

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

Android Pie対応についてまとめ

Android Pieとは

公式:https://developer.android.com/about/versions/pie?hl=ja
Android 9.0のこと。
何故Pieかというと、毎versionでお菓子の愛称を付けているから。
6.0はMarshmallow、7.0はNougat、8.0はOreoだった。

機能的な変更点

参考:【まとめ】Android Pie 9.0の新機能、特徴、レビュー、変更点、不具合

  • ホームボタンが廃止になり、バーになった。iPhone XのようなUXに
  • 最近使用したアプリの表示が新しくなり、よく使うアプリが5つ表示された状態の画面になった
  • アプリドロワーが新しくなり、頻繁に使用されるアプリが一番上に表示されるようになった
  • 「アプリのダッシュボードの表示」を搭載
  • 「アプリタイマー」機能の搭載(アプリの利用時間がグラフィックに表示できる)
  • 端末を平面に置き、画面を下にして起動する「Shushモード」の搭載(自動的に音声通知と振動をミュートに)
  • 「ビール」の絵文字デザイン変更
  • テキスト拡大表示 etc

開発面でのAndroid 9移行対応について

公式:Migrating to Android 9

移行は「基本的な互換性の確認」と「targetSdkVersionを28にする対応」の二つのフェーズがあるとのこと。

基本的な互換性の確認

参考:Behavior changes for all apps
targetSdkVersionを変えなくても、Android 9ではアプリの動作に影響を与える可能性がある。具体的には、以下のような変更点があり、これによってAndroid 9では一部機能が動かないなどの可能性が存在する。

  • non-SDKインターフェースの制限:一部APIでリフレクション経由などでのアクセスがブロックされる
  • Cryptoプロバイダの廃止:SecureRandom.getInstance("SHA1PRNG", "Crypto") を呼び出すと、NoSuchProviderExceptionがスローされる
  • UTF-8デコーダが厳密になり、Unicode標準に準拠するようになる
  • アイドル状態でカメラ、マイク、SensorManagerセンサーにアクセスできなくなる
  • アプリスタンバイバケットの導入:アプリの使用頻度に応じてバケットが割り当てられ、優先度が変化する
  • バッテリーセーバーの改善
  • 電話番号や通話履歴へのアクセスの制限

targetSdkVersionを28にする対応

参考:Behavior changes for apps targeting API 28+

  • フォアグラウンド サービスを使用する際に FOREGROUND_SERVICE パーミッションをリクエストする必要がある
  • ビルドシリアル番号のサポートの終了(Build.SERIALが常に"UNKNOWN"になる)
  • プライベートDNS APIの考慮が必要
  • isCleartextTrafficPermitted()がデフォルトfalseになる。必要に応じてcleartextTrafficPermittedtrueに指定する必要がある
  • アプリが複数のプロセス間で単一のWebViewデータディレクトリを共有できなくなっている
  • ワールドアクセスが可能な Unix パーミッションを使用して、他のアプリとデータを共有できない
  • 現在のデフォルトではないネットワーク上のネットワークトラフィックがカウントされ、そのトラフィックをクエリするために、NetworkStatsManagerクラスのメソッドが提供される
  • Apache HTTP クライアントのサポートが削除される
  • 面積が0のビューはフォーカス不可
  • 4桁および8桁の16進数CSS色を扱うために、CSS Color Module Level 4で策定された動作を有効にする必要がある
  • ドキュメントのルート要素がスクロール要素である場合、適切な処理が実行される
  • 保留されたアプリからの通知が、アプリ再開まで非表示になる

Pie関連のリンク

https://github.com/operando/Notes/tree/master/potatotips_52
https://developers-jp.googleblog.com/2018/10/introducing-android-9-pie.html
https://feel-log.net/android/developer/android_9_pie_migrations_apps/

その他のリンク

[Android]Androidバージョンと対応するAPIレベル/コードネーム一覧
AndroidのAPI LevelとVersionの対応関係一覧表

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

【Android】EditText でのアルファベット大文字制限、長さ制限

EditText でのアルファベット大文字制限

Android アプリでの入力ボックスとして使われる EditText で、
アルファベット大文字のみの入力に制限したい!
iOS の場合は 【iOS】UITextField でのアルファベット大文字制限、長さ制限 を参照してください。

環境

ホスト: Windows 10
Android Studio: 3.3.2
Java

属性の設定で行う場合

レイアウトを開き対象の UITextField を選択して 右ペインの属性:digits を開いて編集します。
image.png

android:digits="ABCDEFGHIJKLMNOPQRSTUVWXYZ"

他の制約もまとめて適用する場合

フィルターの準備

MyActivity.java
    // アルファベットフィルタ
    private  InputFilter alphabetFilter = new InputFilter() {
        @Override
        public CharSequence filter(CharSequence source, int start, int end,
                                   Spanned dest, int dstart, int dend) {
            if (source.toString().matches("^[a-zA-Z]+$")) {
                return source.toString().toUpperCase(Locale.ROOT);
            } else {
                return "";
            }
        }
    };

EditText にフィルター設定

  1. onCreate 関数内などで実装
MyActivity.java
    InputFilter myFilters[] = {alphabetFilter, new InputFilter.LengthFilter(5)}; // 大文字+アルファベットフィルタ+長さフィルタ

    EditText targetEditText = findViewById(R.id.edittext_target);
    targetEditText.setFilters(myFilters);

実機テスト

キーボードにて数字入力ができない事、長さの制限がされていること、
小文字を入力した場合に大文字に自動変換されることを確認します。

以上、お疲れ様でした!

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

EasylauncherをVector Assetsで使う

TL;DR

Easylauncher(https://github.com/akaita/easylauncher-gradle-plugin) というAndroidアプリのランチャーアイコンに付加情報をつけてくれるGradle Pluginがあります。

Adaptive Icon対応もしているのですが、いまのところVector Assetsを使用した場合に対応していません。
LayerDrawableを使用することで、いちおうVector Assetsに対応する事が出来たのでその方法をまとめています。

? Attention

Pixel3のホームアプリ(Pixel Launcher)でしか動作確認していません。他のホームアプリだと動かないかもしれません。

リリースビルドへの影響が気になる場合、リリースビルド用のresディレクトリに変更前のic_launcher.xmlをコピーしておき、リリースビルドではLayerDrawableが使用されないようにしておくと安心です。

? Before

現在のic_launcher.xmlの記述が次の様になっており、<forground>タグでVectorDrawableが指定されています。
このときに<background>タグで指定するDrawableはVectorDrawableでもColorDrawableでも構いません

res/mipmap-v26/ic_launcher.xml
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
    <background android:drawable="@drawable/ic_launcher_background" />
    <foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

? Operation

1.リボン描画用の透過画像を作成

108 x 108 dpの透明なPNGファイルを作成し、drawable_xxdpi/ic_launcher_foreground_ribbonのような名称で保存します。
※ mdpi 〜 xxxdpiまですべての画像を置いても、デバッグ用なので手抜きしてxxdpiだけでもOKです。

↓↓↓↓↓ ココにxxhdpi用の透過画像を貼っています ↓↓↓↓↓
ic_launcher_foreground_ribbon.png
↑↑↑↑↑ ココにxxhdpi用の透過画像を貼っています ↑↑↑↑↑

2.本来のフォアグラウンド画像とリボン描画用の透明画像を重ねたLayerDrawableを作成

次の内容のdrawable/ic_launcher_foreground_with_ribbon.xmlを作成します。

res/drawable/ic_launcher_foreground_with_ribbon.xml
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:drawable="@drawable/ic_launcher_foreground" />
    <item
        android:drawable="@drawable/ic_launcher_foreground_ribbon" />
</layer-list>

3.フォアグラウンド画像としてLayerDrawableを指定する

res/mipmap-v26/ic_launcher.xmlの内容を以下のように変更します。

res/mipmap-v26/ic_launcher.xml
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
    <background android:drawable="@drawable/ic_launcher_background" />
    <foreground android:drawable="@drawable/ic_launcher_foreground_with_ribbon" />
</adaptive-icon>

4.build.gradleにEasylauncher用のスクリプトを追加する

build.gradleのeasylauncherセクションに以下のような内容を記述します。
ここでforegroundIconNamesにリボン描画用の透明画像のDrawableを指定します。

詳細なEasylauncherのカスタマイズ方法についてはgithubのREADMEを参照してください。
https://github.com/akaita/easylauncher-gradle-plugin

app/build.gradle
easylauncher {
    foregroundIconNames "@drawable/ic_launcher_foreground_ribbon"

    buildTypes {
        debug {
            filters = customColorRibbonFilter("開発版", "#DF3A01", "#FFFFFF", "bottom")
        }
    }
}

? Result

device-2019-03-27-024017.png

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

OkHttp3をAndroidで使おうとしたときにjava.lang.ClassCastException: Bootstrap methaod returned nullが発生して初期化に失敗する

TL;DR

E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.example.myapplication, PID: 12443
    java.lang.BootstrapMethodError: Exception from call site #5 bootstrap method
        at okhttp3.internal.Util.<clinit>(Util.java:87)
        at okhttp3.internal.Util.skipLeadingAsciiWhitespace(Util.java:321)
        at okhttp3.HttpUrl$Builder.parse(HttpUrl.java:1313)
        at okhttp3.HttpUrl.get(HttpUrl.java:917)
        at okhttp3.Request$Builder.url(Request.java:165)
        at com.example.myapplication.com.example.myapplication.util.HttpUtil.httpGet(HttpUtil.kt:8)
        at com.example.myapplication.MainActivity$onParallelGetButtonClick$1$1.invokeSuspend(MainActivity.kt:33)
        at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:32)
        at kotlinx.coroutines.DispatchedTask.run(Dispatched.kt:236)
        at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:594)
        at kotlinx.coroutines.scheduling.CoroutineScheduler.access$runSafely(CoroutineScheduler.kt:60)
        at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:742)
     Caused by: java.lang.ClassCastException: Bootstrap method returned null
        at okhttp3.internal.Util.<clinit>(Util.java:87) 
        at okhttp3.internal.Util.skipLeadingAsciiWhitespace(Util.java:321) 
        at okhttp3.HttpUrl$Builder.parse(HttpUrl.java:1313) 
        at okhttp3.HttpUrl.get(HttpUrl.java:917) 
        at okhttp3.Request$Builder.url(Request.java:165) 
        at com.example.myapplication.com.example.myapplication.util.HttpUtil.httpGet(HttpUtil.kt:8) 
        at com.example.myapplication.MainActivity$onParallelGetButtonClick$1$1.invokeSuspend(MainActivity.kt:33) 
        at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:32) 
        at kotlinx.coroutines.DispatchedTask.run(Dispatched.kt:236) 
        at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:594) 
        at kotlinx.coroutines.scheduling.CoroutineScheduler.access$runSafely(CoroutineScheduler.kt:60) 
        at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:742) 

上記の類似のエラーが発生した場合は以下のオプションをbuild.gradleに追加しBuild -> Clean Projectをする

android {
    compileOptions {
        targetCompatibility = "8"
        sourceCompatibility = "8"
    }
}

https://github.com/square/okhttp/issues/4597

事の始まり

Androidは浦島太郎なので、kotlinでも触るかと思って、いろいろと試していた。

APIを叩きたいと思ったのでokhttpを導入してみた。
導入の参考に以下の記事を使わせていただいた。
https://qiita.com/jonghyo/items/bf3e4e06022eebe8e3eb

ところが、実行すると落ちてしまう。
ということで調査をしてみた。
ログには以下のような出力がされていた。

E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.example.myapplication, PID: 12443
    java.lang.BootstrapMethodError: Exception from call site #5 bootstrap method
        at okhttp3.internal.Util.<clinit>(Util.java:87)
        at okhttp3.internal.Util.skipLeadingAsciiWhitespace(Util.java:321)
        at okhttp3.HttpUrl$Builder.parse(HttpUrl.java:1313)
        at okhttp3.HttpUrl.get(HttpUrl.java:917)
        at okhttp3.Request$Builder.url(Request.java:165)
        at com.example.myapplication.com.example.myapplication.util.HttpUtil.httpGet(HttpUtil.kt:8)
        at com.example.myapplication.MainActivity$onParallelGetButtonClick$1$1.invokeSuspend(MainActivity.kt:33)
        at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:32)
        at kotlinx.coroutines.DispatchedTask.run(Dispatched.kt:236)
        at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:594)
        at kotlinx.coroutines.scheduling.CoroutineScheduler.access$runSafely(CoroutineScheduler.kt:60)
        at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:742)
     Caused by: java.lang.ClassCastException: Bootstrap method returned null
        at okhttp3.internal.Util.<clinit>(Util.java:87) 

java.lang.BootstrapMethoderrorって何だろう?と思って公式のドキュメントも見てみたが動的読み込みに失敗したときにおこるということ以上の事はよくわからない。

デバッグしてもなぜか本当に落ちる。よくわからない。

仕方がないのでググる

https://github.com/square/okhttp/issues/4597

すると上記のissueが見つかった。

JakeWhartonさん曰く3.13以上のOkHttpではJava8機能をビルド時に有効にする必要があるとのこと。

OkHttp requires that you enable Java 8 in your builds to function as of 3.13 and newer.

とのことなので、JakeWhartonさんの書いているコードを仕込む。

android {
    compileOptions {
        targetCompatibility = "8"
        sourceCompatibility = "8"
    }
}

これで動くぞやれやれと思っていたら動かなくて焦ったのですが、ちょっと読み進めると動かないよーって言ってる人がいてそうそう動かないよ。とか思ってたのですが、

上記のissueの下の方でeddexさんがbuild.gradleを変更したらBuild -> clean buildを忘れずにやってね。と書いていました。

試してみたところ無事動いてめでたしめでたしという感じでした。

因みに久しぶりすぎて、マニフェストに<uses-permission android:name="android.permission.INTERNET" />足し忘れてまたはまってました。

みんな上記のオプションを仕込むのが当たり前だから、話題になってないんだろうなと思います。

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