ss_t_t_nの日記

開発の備忘録など

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にたどり着く。

WebViewでリダイレクトかどうかを判定する

判定する。

リダイレクトの際にもshouldoverrideurlloadingの中に入ってくるので、
そこは無視したい。

WebView.HitTestResultのUNKNOWN_TYPEの場合はリダイレクトだとする方法で当初は上手くいって見えた。

しかし、リンクテキストをタップした時なんかにもたまにこれが来る時があった。
SRC_ANCHOR_TYPEが通常来るが、たまにUNKNOWN_TYPEで入ってくるという具合。

そのためこの方法は使えず。

色々と調べた結果、リダイレクトの時はonPageFinishedより先にshouldoverrideurlloadingに
入ってくることが判明。

stackoverflow.com

ここで教えてくれている通りにフラグで管理するようにして解決した。
ありがたい・・。

droidkaigiいってきた

行ってきた

仕事の合間に行ったので対して聞けなかったけれども、結構勉強になりました。
英語のスピーチを試しに聞いてみたけど辛いだけだった。英語勉強しなきゃ()

今回は聞いた内の一つ、「実践アニメーション」のサンプルを読む。

xmlで複雑なアニメーションは聖域になるからやめた方が良い、ってのが一番印象に残ってる。

Androidのアニメーションの基本部分の簡単なサンプルたちはスルーして、
花火してたところを見る。

MarbleAnimationってところかなぁ多分


まずは呼び出し側
MarbleAnimationActivity.java

animatorView = (MarbleView) findViewById(R.id.animatorView);

        animatorView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                animatorView.startCustomAnimation();
            }
        });

ほんとに呼ぶだけって感じ。カスタムのView + ValueAnimator最強って言ってたから、
その通りの作りになってるんだな、って。


というわけで本体
MarbleView.java

private void initParams() {
        maxDistance = getContext().getResources().getDimensionPixelSize(R.dimen.marble_max_distance);
        circleRadius = getContext().getResources().getDimensionPixelSize(R.dimen.marble_max_circle_radius);
    }

Viewの拡張クラスの中でgetContextって出来るんだ・・!(無知)
コンストラクタで必ず渡してるんだから当たり前かぁ

上記の個所は初期値を定義している部分で、すべてのコンストラクタで呼び出していた。
外から渡したりはしないんすねーってちょっと思ったけど、設定値を書き換えろやって話か

// 外の公開しているのはこのメソッドだけみたい
 public void startCustomAnimation() {
        if (isRunning) return;

        isRunning = true; // このフラグを折ってるところが見当たらない
        for (int i = 0; i < animators.length; i++) { // animatorsはValueAnimatorの配列で20個
            createAndStart(i);
        }
    }


20個のValueAnimatorを作成してるみたい。
花火の各点になるのかな?なるんじゃないかな?

private ValueAnimator createAnimator() {
        Random random = new Random();
        ValueAnimator animator = ValueAnimator.ofPropertyValuesHolder(
                PropertyValuesHolder.ofFloat(VELOCITY_X, (random.nextFloat() - 0.5f) * 2),
                PropertyValuesHolder.ofFloat(VELOCITY_Y, (random.nextFloat() - 0.5f) * 2),
                PropertyValuesHolder.ofFloat(PROGRESS, 1.0f));
        animator.setDuration(random.nextInt(MAX_DURATION / 2) + MAX_DURATION / 2);
        animator.setInterpolator(new DecelerateInterpolator()); // 最初速くて減速する。花火だ。
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                invalidate(); // これを使ってonDrawを呼び出している様子
            }
        });
        return animator;
    }

一番知りたかった部分・・・かな?
valueAnimatorで値が変化するとonAnimationUpdateが呼ばれるみたい。
そこでinvalidateすることでonDrawを呼んでいる。

setInterpolatorって何だろうと思って調べたけど、スライドにちゃんと書いてた。

private void drawPhotons(Canvas canvas) {
        for (int i = 0; i < animators.length; i++) {
            float velocityX = (float) animators[i].getAnimatedValue(VELOCITY_X);
            float velocityY = (float) animators[i].getAnimatedValue(VELOCITY_Y);
            float progress = (float) animators[i].getAnimatedValue(PROGRESS);
            paints[i].setAlpha(255 - (int) (255 * progress));
            canvas.drawCircle(maxDistance * velocityX + centerOfX(),
                    maxDistance * velocityY + centerOfY(),
                    circleRadius * progress,
                    paints[i]);
        }
    }


最後、onDraw内で呼んでいるメソッド。
ValueAnimatorで動きを定義して、ここで実際の描画を行っているっぽい。
色はcreatePaintメソッドの方でランダムで定義してた。

drawCircleで行っている計算は速度をうまく使ってやってるみたい。
つまり、ランダムで発生した速度を点のサイズに反映することで、速い点はでっかく、
小さい点はちっこくなる・・んじゃないかなぁ。

progressって何だろう・・・

xmlで書くよりは確かに読みやすそう。でも描画を行っている部分が多少複雑になりそう。

これでAndroidのアニメーションが書けるようになった!