Hello, I am new with Kotlin.
I have a published application and try to re-write some code from Java to Kotlin.
I used my implementation of Promise and Deferred objects (my Deferred allows to resolve inner promise from outside) for tts initialization and tts speaking. My purpose is controlling when initialization/speaking started and ended. I hoped that my code in Kotlin will be simpler.
There is working implementations:
class TextToSpeechManager(private val context: Context, private val locale: Locale) {
private val tag = "TTS"
private val utteranceManager = UtteranceManager()
private var ttsDeferred: Deferred<TextToSpeech> = init()
private suspend fun get(): TextToSpeech {
Log.d(tag,"get")
if (ttsDeferred.isCompletedExceptionally) {
ttsDeferred = init()
}
return ttsDeferred.await()
}
private fun init(): Deferred<TextToSpeech> {
Log.d(tag,"init")
return async {
suspendCoroutine<TextToSpeech> { continuation ->
selfReference<TextToSpeech> {
TextToSpeech(this@TextToSpeechManager.context) { status ->
Log.d(tag,"init " + status)
if (status == TextToSpeech.SUCCESS) {
val languageStatus = self.setLanguage(locale)
Log.d(tag,"init " + languageStatus)
when (languageStatus) {
TextToSpeech.LANG_AVAILABLE,
TextToSpeech.LANG_COUNTRY_AVAILABLE,
TextToSpeech.LANG_COUNTRY_VAR_AVAILABLE -> {
self.setOnUtteranceProgressListener(utteranceManager)
continuation.resume(self)
}
else -> continuation.resumeWithException(Exception(languageStatus.toString()))
}
} else {
continuation.resumeWithException(Exception(status.toString()))
}
}
}
}
}
}
suspend fun speak(value: String, queueMode: Int) {
Log.d(tag,"speak " + value)
val tts = get()
suspendCoroutine<Unit>{ continuation ->
val utterance = utteranceManager.create(value, queueMode, continuation)
speak(tts, utterance)
}
}
private fun speak(tts: TextToSpeech, utterance: Utterance) {
Log.d(tag,"speak " + utterance.id)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
tts.speak(utterance.text, utterance.queueMode, null, utterance.id)
} else {
val params = hashMapOf(TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID to utterance.id)
@Suppress("DEPRECATION")
tts.speak(utterance.text, utterance.queueMode, params)
}
}
}
class UtteranceManager : UtteranceProgressListener() {
private val utterances = HashMap<String, Utterance>()
private var nextUtteranceId = 0L
private val tag = "UtteranceManager"
@Synchronized fun create(value: String, queueMode: Int, continuation: Continuation<Unit>): Utterance {
Log.d(tag,"create " + value)
val utteranceId = generateUtteranceId()
val utterance = Utterance(utteranceId, value, queueMode, continuation)
utterances.put(utteranceId, utterance)
return utterance
}
@Synchronized override fun onStart(utteranceId: String) {
Log.d(tag,"onStart " + utteranceId)
val started = utterances.remove(utteranceId) as Utterance
if (started.queueMode == TextToSpeech.QUEUE_FLUSH) {
for (flushed in utterances.values) {
flushed.continuation.resumeWithException(Exception("UTTERANCE_FLUSH"))
}
utterances.clear()
}
utterances.put(utteranceId, started)
}
@Synchronized override fun onDone(utteranceId: String) {
Log.d(tag,"onDone " + utteranceId)
val utterance = utterances.remove(utteranceId) as Utterance
utterance.continuation.resume(Unit)
}
@Synchronized override fun onError(utteranceId: String) {
Log.d(tag,"onError " + utteranceId)
val utterance = utterances.remove(utteranceId) as Utterance
utterance.continuation.resumeWithException(Exception("UTTERANCE_ERROR"))
}
private fun generateUtteranceId(): String {
val utteranceId = nextUtteranceId++
return utteranceId.toString()
}
}
class Utterance(val id: String, val text: String, val queueMode: Int, val continuation: Continuation<Unit>)
// third-party solution
class SelfReference<T> internal constructor(initializer: SelfReference<T>.() -> T) {
val self: T by lazy {
inner ?: throw IllegalStateException("Do not use `self` until `initializer` finishes.")
}
private val inner = initializer()
}
fun <T> selfReference(initializer: SelfReference<T>.() -> T): T {
return SelfReference(initializer).self
}
usage:
val ttsMan = TextToSpeechManager(this, Locale.getDefault())
button.setOnClickListener({
launch {
Log.d("APP", "before speak")
ttsMan.speak(text.text.toString(), TextToSpeech.QUEUE_FLUSH)
Log.d("APP", "after speak")
}
})
This code is working as expected. But I don’t like nesting async → suspendCoroutine, selfReference hack.
Is there more elegant solution with Kotlin and coroutines?