highlight.js に「AndroidStudio」スタイルが登場してた件

WEBサイトでコードをシンタックス・ハイライトするなら「highlight.js」がおすすめです。

highlight.js Android Studio style

👉 highlight.js 

「スタイル」と呼ばれるきれいなテーマが今では 246個 もあるようです。

Androidアプリ向けIDE「AndroidStudio」というスタイルも登場していました。

ダークなほうの「Darcula」ですね、これ。

highlight.js Android Studio style

その他、いくつかメジャーどころを貼っておきます。

github

github-dark

github-dark-dimmed

stackoverflow-light

stackoverflow-dark

使い方

HTMLヘッダーの中に表示したいスタイル「androidstudio」のCSSファイルと、プログラミング言語を認識するJavaScriptファイルについて記述しておきます。


<link rel="stylesheet" href="/path/to/styles/androidstudio.min.css">
<script src="/path/to/highlight.min.js"></script>
<script>hljs.highlightAll();</script>

<pre><code class="language-kotlin"> ~ </code></pre> 内にコードを差し込みます。

言語はここで指定しなくても自動検出してくれるようですが、短いコードなどもあるので、念のため記述しておいたほうが良いでしょう。

実体参照にエスケープしたコードを以下のように差し込み貼り付けます。


<pre><code class="language-kotlin">
import kotlinx.serialization.Serializable
import kotlin.random.Random

interface Building

@Serializable
class House(
    private val rooms: Int? = 3,
    val name: String = &quot;Palace&quot;
) : Building {
    var residents: Int = 4
        get() {
            println(&quot;Current residents: $field&quot;)
            return field
        }

    fun burn(evacuation: (people: Int) -&gt; Boolean) {
        rooms ?: return
        if (evacuation((0..residents).random()))
            residents = 0
    }
}

fun main() {
    val house = House(name = &quot;Skyscraper 1&quot;)
    house.burn {
        Random.nextBoolean()
    }
}

</code></pre>

👉 【macOS】コピーしているテキストをHTMLエスケープする 

ブラウザでアクセスすると以下のように表示されています。


import kotlinx.serialization.Serializable
import kotlin.random.Random

interface Building

@Serializable
class House(
    private val rooms: Int? = 3,
    val name: String = "Palace"
) : Building {
    var residents: Int = 4
        get() {
            println("Current residents: $field")
            return field
        }

    fun burn(evacuation: (people: Int) -> Boolean) {
        rooms ?: return
        if (evacuation((0..residents).random()))
            residents = 0
    }
}

fun main() {
    val house = House(name = "Skyscraper 1")
    house.burn {
        Random.nextBoolean()
    }
}

👉 How to use highlight.js 

このサイトでも使っていましたが6年ぶりに更新してみましたので 🙆



【Retorofit】コピペで使える NetworkModule【Dagger Hilt】

もうこれはテンプレ化しておきます。

Retrofit の話が中心となります。

👉 Retrofit 

 

Converter

Retrofit-Converters

JSON 形式のレスポンスをパースして変換するのは、Gson か Moshi が人気のように思います。


var retrofit = Retrofit.Builder()
  .baseUrl("https://api.example.com")
  .addConverterFactory(GsonConverterFactory.create())
  .build()


var retrofit = Retrofit.Builder()
  .baseUrl("https://api.example.com")
  .addConverterFactory(MoshiConverterFactory.create())
  .build()

👉 retrofit/retrofit-converters at master · square/retrofit 

今回は、Kotlin Serialization を利用した「Kotlin Serialization Converter」を使います。

ExperimentalSerializationApi なのですがね。


val contentType = "application/json".toMediaType()
val retrofit = Retrofit.Builder()
    .baseUrl("https://example.com/")
    .addConverterFactory(Json.asConverterFactory(contentType))
    .build()

👉 JakeWharton/retrofit2-kotlinx-serialization-converter: A Retrofit 2 Converter.Factory for Kotlin serialization. 

 

Call Adapter

Retrofit CallAdapters

通信の非同期処理はどれに任せるか。


var retrofit = Retrofit.Builder()
  .baseUrl("https://api.example.com")
  .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
  .build()

👉 retrofit/retrofit-adapters at master · square/retrofit 

Coroutine を使った Jake製の「Kotlin Coroutine Adapter」は 今では DEPRECATED。

👉 JakeWharton/retrofit2-kotlin-coroutines-adapter: A Retrofit 2 adapter for Kotlin coroutine's Deferred type. 

Retrofit は 2.6.0+ で、内部に suspend function をサポートしてるので、特に、.addCallAdapterFactory() を使わなくてもよい。


@GET("users/{id}")
suspend fun user(@Path("id") id: Long): User

👉 retrofit/CHANGELOG.md at master · square/retrofit 

 

HttpLoggingInterceptor

OkHttpClient に HTTP関連のログを吐かせます。


--> POST /greeting http/1.1
Host: example.com
Content-Type: plain/text
Content-Length: 3

Hi?
--> END POST

<-- 200 OK (22ms)
Content-Type: plain/text
Content-Length: 6

Hello!
<-- END HTTP

👉 HttpLoggingInterceptor.Level (OkHttp Logging Interceptor 3.14.0 API) 


val logging = HttpLoggingInterceptor()
logging.setLevel(Level.BASIC)
val client = OkHttpClient.Builder()
  .addInterceptor(logging)
  .build()

👉 okhttp/okhttp-logging-interceptor at master · square/okhttp 

 

まとめ

Dagger の Module にしておきます。

テンプレート化してください、と言わんばかりに

よく似たものをあちこちで見かけますよね。

見通しも良くなります。

コピペでどうぞ。

(おわり)



LeakCanary 2.8.1 アプリ起動時に NullPointerException

LeakCanary is a memory leak detection library for Android.
👉 LeakCanary 

アプリが起動できずにこんなの出ましたけど。


2022-02-28 21:18:29.578 16816-16816/com.benigumo.apn D/AndroidRuntime: Shutting down VM
2022-02-28 21:18:29.589 16816-16816/com.benigumo.apn E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.example.app, PID: 16816
    java.lang.NullPointerException
        at q5.c$c.b(:112)
        at q5.c$c.f(:154)
        at p5.f.d(:233)
        at c7.x$a.c(:46)
        at p5.b$a.a(:38)
        at c7.x$a.b(:43)
        at q5.b$b.a(:25)
        at q5.b$b.add(:23)
        at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:393)
        at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:133)
        at android.app.ActivityThread.handleResumeActivity(ActivityThread.java:4847)
        at android.app.servertransaction.ResumeActivityItem.execute(ResumeActivityItem.java:54)
        at android.app.servertransaction.ActivityTransactionItem.execute(ActivityTransactionItem.java:45)
        at android.app.servertransaction.TransactionExecutor.executeLifecycleState(TransactionExecutor.java:176)
        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:97)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2214)
        at android.os.Handler.dispatchMessage(Handler.java:106)
        at android.os.Looper.loopOnce(Looper.java:201)
        at android.os.Looper.loop(Looper.java:288)
        at android.app.ActivityThread.main(ActivityThread.java:7842)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1003)

minifyEnabled false ではクラッシュしない。

回避方法

難読化されている部分は以下の模様。


Fatal Exception: java.lang.NullPointerException
curtains.internal.WindowCallbackWrapper$Companion.getJetpackWrapped (WindowCallbackWrapper.kt:112)
curtains.internal.WindowCallbackWrapper$Companion.unwrap (WindowCallbackWrapper.kt:154)
curtains.WindowsKt.getWrappedCallback (Windows.kt:233)
leakcanary.RootViewWatcher$listener$1.onRootViewAdded (RootViewWatcher.kt:46)
curtains.OnRootViewAddedListener$DefaultImpls.onRootViewsChanged (Listeners.kt:38)
leakcanary.RootViewWatcher$listener$1.onRootViewsChanged (RootViewWatcher.kt:43)
curtains.internal.RootViewsSpy$delegatingViewList$1.add (RootViewsSpy.kt:25)
curtains.internal.RootViewsSpy$delegatingViewList$1.add (RootViewsSpy.kt:23)
android.view.WindowManagerGlobal.addView (WindowManagerGlobal.java:454)
android.view.WindowManagerImpl.addView (WindowManagerImpl.java:121)
...

👉 Obfucation and 2.8.1 version cause an app launch crash · Issue #2286 · square/leakcanary 

書いてあるように、proguard rule に以下を追加で暫定回避できました。


-keep class androidx.appcompat.view.WindowCallbackWrapper { *; }
-keep class android.support.v7.view.WindowCallbackWrapper { *; }

次期バージョンで修正され、不要になるのでしょうが。

👉 Add Proguard rules for WindowCallbackWrapper by carlonzo · Pull Request #33 · square/curtains 

2022-04-08 追記

さらに更新されてる。


-keep class androidx.appcompat.view.WindowCallbackWrapper {
    android.view.Window$Callback mWrapped;
}

-keep class android.support.v7.view.WindowCallbackWrapper {
    android.view.Window$Callback mWrapped;
}

👉 just keep it · carlonzo/curtains@686b12c hatena-bookmark