ss_t_t_nの日記

開発の備忘録など

TabLayoutのタブ画像を通信して取得する

画面に表示されている範囲だけ画像を取ってきたい。
また、タブのスクロールに合わせて、適宜画像を取得する。

1. 画面に表示されている範囲のタブだけ画像を取ってくるメソッドを定義する

	private void loadTabImage() {
                 // mScrollBoundsはメンバ変数
		this.getHitRect(mScrollBounds);
		for (int i = 0; i < getTabCount(); i++) {
			final Tab t = getTabAt(i);

                         // setCustomViewを事前に行う必要がある
			final View view = t.getCustomView();
                         // タグの有無で画像ロード済みのタブかどうか判断している
			if (t.getTag() != null) {
				continue;
			}
			if(!view.getLocalVisibleRect(mScrollBounds)) {
				continue;
			}
                         // 画像とってくる処理をここに書く
		}
	}

2. TabLayoutを継承したカスタムTabLayoutを作り、computeScrollをoverrideして、1のメソッドを呼ぶ

	@Override
	public void computeScroll() {
		super.computeScroll();
		loadTabImage();
	}

一通りのOSで確認したが、これで動いているように見える。
200個ほどタブを定義しても平気だった。

targetSdkVersion28が必須へ

先年に実施された、AndroidのtargetSdkVersion26以上必須化ですが、
次はいつなんだ・・とそわそわしていたら、Android Developers Blogに記事が上がりました。

android-developers.googleblog.com


新規アプリは2019/08から、更新アプリは2019/11からみたいですね。

毎年この月って感じになるのかなー。

しかし去年、今年と続けてこの対応が大変だった・・・。
targetSdkVersionを10から26に!?できらぁ!・・え?FCMへの移行も同時に?・・・

率直に言って胃に穴があきそうでした。
こういうのはちゃんと計画してきちんと追随していきましょうねって教訓ですね。

まぁ僕がコントロールできる範囲ではないのですが。言っていくことは出来る。

flutterでWeb画面のタイトルタグの中身をjavascript経由で取得する

class WebViewScreen extends StatefulWidget {

  final String url;

  const WebViewScreen({Key key, this.url}) : super(key: key);

  @override
  _WebViewScreenState createState() => _WebViewScreenState();
}

class _WebViewScreenState extends State<WebViewScreen> {

  FlutterWebviewPlugin _plugin = FlutterWebviewPlugin();

  StreamSubscription _onStateChanged;

  String _title = "";

  @override
  void initState() {
    super.initState();
    _plugin.close();
    _onStateChanged = _plugin.onStateChanged.listen((WebViewStateChanged state) async {
      if (state.type == WebViewState.finishLoad) {
        if (!mounted) {
          return;
        }
        // ココで取得している
        _title = await _plugin.evalJavascript("document.title");
        setState(() {
          print(_title);
        });
      }
    });
  }

  @override
  void dispose() {
    _onStateChanged.cancel();
    _plugin.close();
    super.dispose();
  }


  @override
  Widget build(BuildContext context) {
    return WebviewScaffold(
      url: widget.url,
      withJavascript: true,
      appBar: AppBar(title: Text(_title)),
    );
  }
}

permission 覚え書き

こういうコード断片を適宜残すのは大切だと感じた

インタフェース

interface RuntimePermissionHandler<T> {

	companion object {
		const val REQUEST_CAMERA = 1
	}

	var controller: T?
	var onPermissionOk: (() -> Unit)?
	var onPermissionNg: ((message: String?) -> Unit)?

	fun requestPermission()
	fun handlePermissionResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray)
}

実装

class CameraPermissionHandler(override var controller: Activity?, override var onPermissionOk: (() -> Unit)?) : RuntimePermissionHandler<Activity> {
	override var onPermissionNg: ((message: String?) -> Unit)? = null

	override fun requestPermission() {
		if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
			onPermissionOk?.invoke()
			return
		}
		controller?.let {
			if (ContextCompat.checkSelfPermission(it, CAMERA) == PackageManager.PERMISSION_GRANTED) {
				onPermissionOk?.invoke()
				return
			}
			ActivityCompat.requestPermissions(it, arrayOf(CAMERA), RuntimePermissionHandler.REQUEST_CAMERA)
		}
	}

	override fun handlePermissionResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
		if (requestCode == RuntimePermissionHandler.REQUEST_CAMERA && grantResults.isNotEmpty()) {
			if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
				onPermissionOk?.invoke()
			} else {
				onPermissionNg?.invoke("fail!!!!!!!!!!")
			}
		}
	}

}

呼ぶ

class MainActivity : AppCompatActivity() {

	private var mCameraPermissionHandler: RuntimePermissionHandler<Activity>? = null

	override fun onCreate(savedInstanceState: Bundle?) {
		super.onCreate(savedInstanceState)
		setContentView(R.layout.activity_main)
		mCameraPermissionHandler = CameraPermissionHandler(this, { init() })
		mCameraPermissionHandler?.onPermissionNg = {
			message ->
			message?.let {
				Snackbar.make(root, it, Snackbar.LENGTH_SHORT).show()
			}
		}
		mCameraPermissionHandler?.requestPermission()
	}

	override fun onDestroy() {
		super.onDestroy()
		mCameraPermissionHandler = null
	}

	override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
		super.onRequestPermissionsResult(requestCode, permissions, grantResults)
		mCameraPermissionHandler?.handlePermissionResult(requestCode, permissions, grantResults)
	}

	private fun init() {
		val s = Snackbar.make(root, "init!", Snackbar.LENGTH_LONG)
		s.setAction("reInit", {
			v ->
			val ss = Snackbar.make(v, "reinit!!!!!", Snackbar.LENGTH_SHORT)
			ss.show()
		})
		s.show()
	}

}

使ってみたら、イマイチな箇所があったので更新・・。
shouldShowRequestPermissionRationaleの使い方がいまいちピンと来ない・・。

Espressoの初期設定がうまくできない時は

f:id:shsato_t_t_n:20180221234143p:plain

結構悩んだので備忘録として残しておきます。
単純なことだったのですが、kotlinが原因だと思い込んであれこれ無駄に調べ回っては
古い情報を試してうんうん悩んでしまいました。

通常通りAndroidStudioでプロジェクトを作成した場合、プロジェクトの設定などを
追加で書き換えたりする必要は現状無いようです。

では何をしなければならないのかと言うと
テストのConfigをjUnitではなく、Android Instrumented Tests で作成する必要がありました。
(わかってみればそりゃそうだって感じですが)

悩みました・・。

〜に依存している

ずっと、「〜に依存している」というワードの使い方がわからなかったのですが、
最近、クリーンアーキテクチャについて学ぶ中で、こういうことかーと思ったので喜びを書きます。

僕がこのワードの使い方がわからなかった理由は、「〜に依存している」と言う時に、
具体的に何を指すのかが文脈によってころころ変わるからだったようです。
ある時にわかったと思っても、違う時には全然違うものを指したりするから、
あれ?・・となる。

クリーンアーキテクチャで例の玉ねぎに沿ったパッケージ構成を作成し、そのパッケージ間を
つなぐインタフェース役のクラスを作成した時に唐突に理解しました。

つまり、Javaで言えば、
「AクラスがBクラスをimportしている場合、AクラスはBクラスに依存している」と言えるのだなと。

これまで、処理の分割など自分の中でルールがあまりなく、ふわふわでそれらしく実装していたのですが、
これからはきちんと根拠を持って出来そうです。すごく嬉しい。
勝手に増えるやつくらいにしか思っていなかったimportの大事さが分かりました。

それに、慣れない言語で書く時なんかに一つの指針とできそうなのも嬉しいです。
残業続きの中、思わぬ幸運を拾った思いです。

Kotlinでクラスのnameを取得するには

Android開発の際、ログを出す時やfragmentのtagによくクラス名を使う。
kotlinでも同じようにしようと思ったのだけど、下記のようにすると

MainActivity::javaClass.name

単に「javaClass」とログに出てきて悲しい感じに・・

MainActivity::class.java.name

とすると期待した結果になった。
下の書き方だと、最終的にClass.javaのgetNameにたどり着く。