【kotlin】Matcher.find()を呼ばないとMatcher.start()もMatcher.end()も正常に動作しない

kotlinのmatcherの使い方で非常に時間がかかったのでメモ。
主にMatcherの使い方について。
Matcher.find()を呼ばないとMatcher.start()もMatcher.end()も正常に動作せず、クラッシュを招きます。


やりたかったこと

僕が個人開発しているAndroidアプリTwitMorseのタイムラインで

  • ハッシュタグの色付け
  • ハッシュタグをタップしたらハッシュタグ専用画面への遷移

でした。

TwitMorseにハッシュタグ追随機能追加

問題があったコード

        /**
         * ハッシュタグを抽出してタップできるようにする
         * タップするとHashTagSearchResultActivityに遷移する
         * いずれStringUtilなどのUtilクラスにまとめられるようにtextViewも渡す。
         */
        fun onTapHashTag(text: String, textView: TextView) {
            textView.movementMethod = LinkMovementMethod.getInstance() //ClickableSpan#onClickを動かすのに必要
            val spannableString = SpannableStringBuilder(text)
            val hashTagRegex = Regex("(?:^|\s)([##]([^\s]+))[^\s]?")
            val matcher = Pattern.compile(hashTagRegex.toString()).matcher(text)
            // 参考 : https://qiita.com/droibit/items/75416c0955b797931bb8#kotlintext
            hashTagRegex.findAll(text)
                    .map { it.value }
                    .forEach {
                        val clickableSpan = object : ClickableSpan() {

                            override fun onClick(widget: View) {
                                val hashTagSearchResultActivity = Intent(widget.context, HashTagSearchResultActivity::class.java)
                                hashTagSearchResultActivity.putExtra(IntentKeyUtil.HASH_TAG_SEARCH_QUERY, it)
                                hashTagSearchResultActivity.flags = Intent.FLAG_ACTIVITY_NEW_TASK
                                widget.context.startActivity(hashTagSearchResultActivity)
                            }

                            override fun updateDrawState(ds: TextPaint) {
                                super.updateDrawState(ds)
                                ds.isUnderlineText = false
                            }
                        }
                        // ここが問題の箇所
                        spannableString.setSpan(clickableSpan, matcher.start(), matcher.end(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
                        textView.text = spannableString
                    }
        }

ちょっと分かりづらいと思いますがコメントを入れている箇所が該当箇所。クラッシュ
ここで

java.lang.IllegalStateException: No successful match so far

という例外が発生する

修正

if (matcher.find()) {
    spannableString.setSpan(clickableSpan, matcher.start(), matcher.end(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
    textView.text = spannableString
}

matcher.find()を呼んであげるとクラッシュしなくなりました。

完成コード

        /**
         * ハッシュタグを抽出してタップできるようにする
         * タップするとHashTagSearchResultActivityに遷移する
         * いずれStringUtilなどのUtilクラスにまとめられるようにtextViewも渡す。
         */
        fun onTapHashTag(text: String, textView: TextView) {
            textView.movementMethod = LinkMovementMethod.getInstance() //ClickableSpan#onClickを動かすのに必要
            val spannableString = SpannableStringBuilder(text)
            val hashTagRegex = Regex("(?:^|\s)([##]([^\s]+))[^\s]?")
            val matcher = Pattern.compile(hashTagRegex.toString()).matcher(text)
            // 参考 : https://qiita.com/droibit/items/75416c0955b797931bb8#kotlintext
            hashTagRegex.findAll(text)
                    .map { it.value }
                    .forEach {
                        val clickableSpan = object : ClickableSpan() {

                            override fun onClick(widget: View) {
                                val hashTagSearchResultActivity = Intent(widget.context, HashTagSearchResultActivity::class.java)
                                hashTagSearchResultActivity.putExtra(IntentKeyUtil.HASH_TAG_SEARCH_QUERY, it)
                                hashTagSearchResultActivity.flags = Intent.FLAG_ACTIVITY_NEW_TASK
                                widget.context.startActivity(hashTagSearchResultActivity)
                            }

                            override fun updateDrawState(ds: TextPaint) {
                                super.updateDrawState(ds)
                                ds.isUnderlineText = false
                            }
                        }
                        if (matcher.find()) {
                            spannableString.setSpan(clickableSpan, matcher.start(), matcher.end(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
                            textView.text = spannableString
                        }
                    }
        }

これ以外にもmatcherで無理やり頑張ろうとして1日半くらいを費やしました。

hashTagRegex.findAll(text).map { it.value }
    .forEach {}

↑もmatcher使ってfindするより思い通りの挙動をしてくれたので忘れないようにしたいです。
以上、個人的なメモでした


参考資料

コメント

タイトルとURLをコピーしました