fix(android): app stuck on splash screen from main-thread string loading

PlaybackService.onCreate runs on the main thread and called
runBlocking { getString(...) } for its notification strings. Compose
resources share one in-flight load per strings file (AsyncCache creates the
Deferred in the caller's scope); when App()'s stringResource started that
load on the main dispatcher first, blocking main to await it deadlocked and
the first frame never rendered. Timing-dependent, so mostly seen under the
debugger.

Create the notification channel immediately under the app name and update
its localized name/description from serviceScope. The short-lived
placeholder notification no longer carries localized text.
This commit is contained in:
Kingkor Roy Tirtho 2026-10-20 16:03:48 +06:00
parent d4961bb5ed
commit 8bbefdfb7d
22 changed files with 24 additions and 35 deletions

View File

@ -51,7 +51,6 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
@ -59,7 +58,6 @@ import org.jetbrains.compose.resources.getString
import spotube.composeapp.generated.resources.Res
import spotube.composeapp.generated.resources.notification_playback_channel_description
import spotube.composeapp.generated.resources.notification_playback_channel_name
import spotube.composeapp.generated.resources.notification_playback_service_text
@OptIn(UnstableApi::class)
class PlaybackService : MediaLibraryService(), KoinComponent {
@ -96,9 +94,10 @@ class PlaybackService : MediaLibraryService(), KoinComponent {
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
// Short-lived placeholder until Media3 posts the playback notification. No localized text:
// loading it here would block the main thread (see createNotificationChannel).
val notification = NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("Spotube")
.setContentText(runBlocking { getString(Res.string.notification_playback_service_text) })
.setSmallIcon(R.drawable.ic_stat_spotube)
.setContentIntent(sessionActivity)
.setOngoing(true)
@ -125,21 +124,32 @@ class PlaybackService : MediaLibraryService(), KoinComponent {
Log.i(TAG, "Library session created")
}
/**
* The channel must exist before startForeground, so it's created right away under the app name
* and renamed once the localized strings load.
*
* Never `runBlocking { getString(...) }` here: onCreate runs on the main thread, and Compose
* resources share one in-flight load per strings file. If App()'s stringResource started that
* load on the main dispatcher first, blocking main to await it deadlocks the app on its
* splash screen.
*/
@SuppressLint("ObsoleteSdkInt")
private fun createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(
CHANNEL_ID,
runBlocking { getString(Res.string.notification_playback_channel_name) },
NotificationManager.IMPORTANCE_LOW
).apply {
description = runBlocking {
getString(Res.string.notification_playback_channel_description)
}
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return
val manager = getSystemService(NotificationManager::class.java)
fun channel(name: String, description: String? = null) =
NotificationChannel(CHANNEL_ID, name, NotificationManager.IMPORTANCE_LOW).apply {
this.description = description
setShowBadge(false)
}
val manager = getSystemService(NotificationManager::class.java)
manager.createNotificationChannel(channel)
manager.createNotificationChannel(channel("Spotube"))
serviceScope.launch {
// Re-creating a channel with the same ID updates its name and description.
manager.createNotificationChannel(
channel(
name = getString(Res.string.notification_playback_channel_name),
description = getString(Res.string.notification_playback_channel_description)))
}
}

View File

@ -938,7 +938,6 @@
<string name="app_window_close">إغلاق</string>
<!-- Android playback notification -->
<string name="notification_playback_service_text">خدمة التشغيل</string>
<string name="notification_playback_channel_name">التشغيل</string>
<string name="notification_playback_channel_description">عناصر التحكم في تشغيل الوسائط</string>

View File

@ -914,7 +914,6 @@
<string name="app_window_close">বন্ধ করুন</string>
<!-- Android playback notification -->
<string name="notification_playback_service_text">প্লেব্যাক সার্ভিস</string>
<string name="notification_playback_channel_name">প্লেব্যাক</string>
<string name="notification_playback_channel_description">মিডিয়া প্লেব্যাক কন্ট্রোল</string>

View File

@ -914,7 +914,6 @@
<string name="app_window_close">Schließen</string>
<!-- Android playback notification -->
<string name="notification_playback_service_text">Wiedergabedienst</string>
<string name="notification_playback_channel_name">Wiedergabe</string>
<string name="notification_playback_channel_description">Steuerung der Medienwiedergabe</string>

View File

@ -920,7 +920,6 @@
<string name="app_window_close">Cerrar</string>
<!-- Android playback notification -->
<string name="notification_playback_service_text">Servicio de reproducción</string>
<string name="notification_playback_channel_name">Reproducción</string>
<string name="notification_playback_channel_description">Controles de reproducción multimedia</string>

View File

@ -914,7 +914,6 @@
<string name="app_window_close">بستن</string>
<!-- Android playback notification -->
<string name="notification_playback_service_text">سرویس پخش</string>
<string name="notification_playback_channel_name">پخش</string>
<string name="notification_playback_channel_description">کنترل‌های پخش رسانه</string>

View File

@ -920,7 +920,6 @@
<string name="app_window_close">Fermer</string>
<!-- Android playback notification -->
<string name="notification_playback_service_text">Service de lecture</string>
<string name="notification_playback_channel_name">Lecture</string>
<string name="notification_playback_channel_description">Commandes de lecture multimédia</string>

View File

@ -913,7 +913,6 @@
<string name="app_window_close">बंद करें</string>
<!-- Android playback notification -->
<string name="notification_playback_service_text">प्लेबैक सेवा</string>
<string name="notification_playback_channel_name">प्लेबैक</string>
<string name="notification_playback_channel_description">मीडिया प्लेबैक कंट्रोल</string>

View File

@ -908,7 +908,6 @@
<string name="app_window_close">Tutup</string>
<!-- Android playback notification -->
<string name="notification_playback_service_text">Layanan pemutaran</string>
<string name="notification_playback_channel_name">Pemutaran</string>
<string name="notification_playback_channel_description">Kontrol pemutaran media</string>

View File

@ -920,7 +920,6 @@
<string name="app_window_close">Chiudi</string>
<!-- Android playback notification -->
<string name="notification_playback_service_text">Servizio di riproduzione</string>
<string name="notification_playback_channel_name">Riproduzione</string>
<string name="notification_playback_channel_description">Controlli di riproduzione multimediale</string>

View File

@ -908,7 +908,6 @@
<string name="app_window_close">閉じる</string>
<!-- Android playback notification -->
<string name="notification_playback_service_text">再生サービス</string>
<string name="notification_playback_channel_name">再生</string>
<string name="notification_playback_channel_description">メディア再生コントロール</string>

View File

@ -908,7 +908,6 @@
<string name="app_window_close">닫기</string>
<!-- Android playback notification -->
<string name="notification_playback_service_text">재생 서비스</string>
<string name="notification_playback_channel_name">재생</string>
<string name="notification_playback_channel_description">미디어 재생 컨트롤</string>

View File

@ -914,7 +914,6 @@
<string name="app_window_close">Sluiten</string>
<!-- Android playback notification -->
<string name="notification_playback_service_text">Afspeelservice</string>
<string name="notification_playback_channel_name">Afspelen</string>
<string name="notification_playback_channel_description">Bediening voor media-afspelen</string>

View File

@ -926,7 +926,6 @@
<string name="app_window_close">Zamknij</string>
<!-- Android playback notification -->
<string name="notification_playback_service_text">Usługa odtwarzania</string>
<string name="notification_playback_channel_name">Odtwarzanie</string>
<string name="notification_playback_channel_description">Sterowanie odtwarzaniem multimediów</string>

View File

@ -920,7 +920,6 @@
<string name="app_window_close">Fechar</string>
<!-- Android playback notification -->
<string name="notification_playback_service_text">Serviço de reprodução</string>
<string name="notification_playback_channel_name">Reprodução</string>
<string name="notification_playback_channel_description">Controles de reprodução de mídia</string>

View File

@ -926,7 +926,6 @@
<string name="app_window_close">Закрыть</string>
<!-- Android playback notification -->
<string name="notification_playback_service_text">Служба воспроизведения</string>
<string name="notification_playback_channel_name">Воспроизведение</string>
<string name="notification_playback_channel_description">Управление воспроизведением</string>

View File

@ -908,7 +908,6 @@
<string name="app_window_close">ปิด</string>
<!-- Android playback notification -->
<string name="notification_playback_service_text">บริการเล่นเพลง</string>
<string name="notification_playback_channel_name">การเล่น</string>
<string name="notification_playback_channel_description">ตัวควบคุมการเล่นสื่อ</string>

View File

@ -914,7 +914,6 @@
<string name="app_window_close">Kapat</string>
<!-- Android playback notification -->
<string name="notification_playback_service_text">Çalma hizmeti</string>
<string name="notification_playback_channel_name">Çalma</string>
<string name="notification_playback_channel_description">Medya çalma kontrolleri</string>

View File

@ -926,7 +926,6 @@
<string name="app_window_close">Закрити</string>
<!-- Android playback notification -->
<string name="notification_playback_service_text">Служба відтворення</string>
<string name="notification_playback_channel_name">Відтворення</string>
<string name="notification_playback_channel_description">Керування відтворенням</string>

View File

@ -908,7 +908,6 @@
<string name="app_window_close">Đóng</string>
<!-- Android playback notification -->
<string name="notification_playback_service_text">Dịch vụ phát nhạc</string>
<string name="notification_playback_channel_name">Phát nhạc</string>
<string name="notification_playback_channel_description">Điều khiển phát nội dung</string>

View File

@ -908,7 +908,6 @@
<string name="app_window_close">關閉</string>
<!-- Android playback notification -->
<string name="notification_playback_service_text">播放服務</string>
<string name="notification_playback_channel_name">播放</string>
<string name="notification_playback_channel_description">媒體播放控制項</string>

View File

@ -908,7 +908,6 @@
<string name="app_window_close">关闭</string>
<!-- Android playback notification -->
<string name="notification_playback_service_text">播放服务</string>
<string name="notification_playback_channel_name">播放</string>
<string name="notification_playback_channel_description">媒体播放控制</string>