I'm trying to store boolean in DataStore.
class OnboardingPrefs(val context: Context) {
companion object {
const val ON_BOARDING_PREFS = "on_boarding_prefs"
const val ONBOARD_KEY = "onBoard"
}
private val Context.dataStore: DataStore<Preferences> by preferencesDataStore(
name = ON_BOARDING_PREFS
)
suspend fun saveOnboarding(save: Boolean) {
context.dataStore.edit { preferences ->
preferences[preferencesKey<Boolean>(ONBOARD_KEY)] = save
}
}
}
Problem is preferencesKey cannot be resolved.
Gradle:
implementation 'androidx.datastore:datastore-preferences:1.0.0'
Related
I try to migrate below code which RxJava to Kotlin coroutines.
This uses uses RxJava Scheduler.Worker to do json parsin in own thread. Is there something similar in Kotlin Coroutines?
// RxJava
class MessagesRepository() {
private val messagesSubject = PublishSubject.create<Message>()
val messages = messagesSubject.toFlowable(BackpressureStrategy.BUFFER)
val scheduler = Schedulers.computation()
private fun setClientCallbacks() {
val worker = scheduler.createWorker()
val processMessage = { message: ApiMessage ->
worker.schedule {
val msg = moshiJsonAdapter.fromJson(message.toString())
messagesSubject.onNext(msg)
}
}
client.setCallback(object : Callback {
override fun messageArrived(topic: String, message: ApiMessage) {
processMessage(message)
}
})
}
}
// Coroutines
class MessagesRepository() {
private val _messages = MutableSharedFlow<Message>(
extraBufferCapacity = 1,
onBufferOverflow = BufferOverflow.DROP_OLDEST
)
val messages: SharedFlow<Message> = _messages.asSharedFlow()
private fun setClientCallbacks() {
client.setCallback(object : Callback {
override fun messageArrived(topic: String, message: ApiMessage) {
// How to move this json parsin to own thread
val msg = moshiJsonAdapter.fromJson(message.toString())
_messages.tryEmit(vehicle)
}
})
}
}
I am trying to get some values from a Gradle build into some Kotlin code.
For example, if my code was:
const val compileDate = THE_DATE_THAT_THE_CODE_WAS_COMPILED_ON;
const val compileBranch = THE_GIT_BRANCH_THAT_THE_CODE_WAS_COMPILED_FROM;
fun main() {
println("$compileDate - $compileBranch")
}
Is there any way to get those constants embedded into the code from the build itself?
If its not possible in that manner, I would think it might be possible using annotations, but again - I don't know how.
Any help would be appreciated.
Thank you.
Using annotations
So I found a solution by writing an annotation processor. Here's a fairly minimal working example:
./annotations/src/main/kotlin/net/example/CompileTimeValue.kt:
package net.example
#Retention(AnnotationRetention.SOURCE)
#MustBeDocumented
annotation class CompileTimeValue(val of : String)
./annotations/build.gradle.kts:
plugins {
kotlin("jvm")
}
./processor/src/main/kotlin/net/example/CompileTimeEvaluator.kt:
package net.example
import com.google.auto.service.AutoService
import java.io.File
import java.io.IOException
import javax.annotation.processing.*
import javax.lang.model.SourceVersion
import javax.lang.model.element.Element
import javax.lang.model.element.TypeElement
#AutoService(Processor::class)
#SupportedSourceVersion(SourceVersion.RELEASE_8)
#SupportedOptions(CompileTimeEvaluator.KAPT_OPTION)
class CompileTimeEvaluator : AbstractProcessor() {
private val assignments = mutableMapOf<Element, String?>()
override fun getSupportedAnnotationTypes(): MutableSet<String> =
mutableSetOf(CompileTimeValue::class.java.name)
override fun getSupportedSourceVersion(): SourceVersion =
SourceVersion.latest()
override fun process(set: MutableSet<out TypeElement>?, env: RoundEnvironment?): Boolean {
env?.getElementsAnnotatedWith(CompileTimeValue::class.java)?.forEach {
val note = it.getAnnotation(CompileTimeValue::class.java) as CompileTimeValue
assignments[it] = outputOfCommand(note)
}
val kaptGeneratedDir = processingEnv.options[KAPT_OPTION]
val file = File(kaptGeneratedDir, "ComputedConstants.kt")
file.writeText(generatedClass())
return true
}
private fun generatedClass() : String = """
object ComputedConstants {
fun load() {
${loadAssignments()}
}
}
""".trimIndent()
private fun loadAssignments() : String =
assignments.entries.joinToString("\n") {
val element = it.key
val value = it.value
val elementName = element.simpleName.toString()
// TODO: I think a recursive solution to find the true parent class
// is necessary here to deal with nested classes
val elementNest = element.enclosingElement.simpleName.toString()
val elementPack = processingEnv.elementUtils.getPackageOf(element)
val fullName = "$elementPack.$elementNest.$elementName"
if (value != null)
"$fullName = \"\"\"$value\"\"\""
else
"$fullName = null"
}
private fun outputOfCommand(note : CompileTimeValue) : String? = try {
val process = ProcessBuilder(*note.of.split(' ').toTypedArray())
.redirectOutput(ProcessBuilder.Redirect.PIPE)
.redirectError(ProcessBuilder.Redirect.PIPE)
.start()
process.waitFor()
process.inputStream.bufferedReader().readText().trimEnd('\n')
} catch (e : IOException) {
null
}
companion object {
const val KAPT_OPTION = "kapt.kotlin.generated"
}
}
./processor/build.gradle.kts:
plugins {
kotlin("jvm")
kotlin("kapt")
}
kapt {
correctErrorTypes = true
}
dependencies {
"kapt"(project(":annotations"))
compileOnly(project(":annotations"))
implementation(fileTree("libs").include("*.jar"))
implementation("com.google.auto.service:auto-service:1.0-rc6")
annotationProcessor("com.google.auto.service:auto-service:1.0-rc6")
}
./example/src/main/kotlin/net/example/Example.kt:
package net.example
object Constants {
#field:CompileTimeValue("date")
var compileDate : String? = "Unknown"
}
fun main() {
ComputedConstants.load()
println("Compiled on: ${Constants.compileDate}")
}
./example/build.gradle.kts:
plugins {
kotlin("jvm")
kotlin("kapt")
application
}
application {
mainClass.set("net.example.ExampleKt")
}
dependencies {
implementation(project(":annotations"))
"kapt"(project(":annotations"))
"kapt"(project(":processor"))
}
./example/build/generated/source/kaptKotlin/main/ComputedConstants.kt:
object ComputedConstants {
fun load() {
net.example.Constants.compileDate = """Sat 27 Mar 2021 01:30:46 PM EDT"""
}
}
./build.gradle.kts:
plugins {
// apply(false) so that it can be re-used in children
kotlin("jvm") version "1.4.31" apply(false)
kotlin("kapt") version "1.4.31" apply(false)
}
allprojects {
repositories {
jcenter()
mavenCentral()
maven { url = uri("https://dl.bintray.com/kotlin/kotlin-js-wrappers") }
maven { url = uri("https://dl.bintray.com/kotlin/kotlinx") }
maven { url = uri("https://dl.bintray.com/kotlin/ktor") }
}
}
./settings.gradle.kts:
include(":example")
include(":annotations")
include(":processor")
Another quick way is to use the gradle copy task to inflate a kotlin source template.
Create a kotlin template file App.kt some where in the source tree (src/main/templates/App.kt)
object App {
const val VERSION = "$version"
const val BASE_VERSION = "$base_version"
}
Create a copy task in your build file
val copyTemplates by registering(Copy::class) {
filteringCharset = "UTF-8"
from(project.projectDir.resolve("src/main/templates"))
into(project.buildDir.resolve("generated-sources/templates/kotlin/main"))
}
Add the generated template to the main sourceset
sourceSets {
main {
java.srcDirs(copyTemplates)
}
}
Now you can access App as regular kotlin object from your classes
fun main() {
println("Version is : ${App.VERSION}")
}
I started using the library for routes in an android application. There was such a problem, I don't know how to solve it.
Type mismatch.
Required:
Creator<Context, Intent>
Found:
() → Intent
Library used Cicerone. I created my object class Screens and according to some sources made an implementation like this
import com.csproject.rflex.app.App
import com.csproject.rflex.presentation.launch.LaunchActivity
import com.github.terrakok.cicerone.androidx.ActivityScreen
object Screens {
fun launch() = ActivityScreen {
LaunchActivity.newIntent(App.instance.getAppContext())
}
}
Activity code fragment
class LaunchActivity: ABaseActivity(), ILaunchView {
companion object{
fun newIntent(context: Context) = Intent(context, LaunchActivity::class.java)
}
Libriry class
sealed class AppScreen : Screen
fun interface Creator<A, R> {
fun create(argument: A): R
}
open class FragmentScreen #JvmOverloads constructor(
private val key: String? = null,
private val fragmentCreator: Creator<FragmentFactory, Fragment>
) : AppScreen() {
override val screenKey: String get() = key ?: super.screenKey
fun createFragment(factory: FragmentFactory) = fragmentCreator.create(factory)
}
open class ActivityScreen #JvmOverloads constructor(
private val key: String? = null,
private val intentCreator: Creator<Context, Intent>
) : AppScreen() {
override val screenKey: String get() = key ?: super.screenKey
open val startActivityOptions: Bundle? = null
fun createIntent(context: Context) = intentCreator.create(context)
}
UPDATE #1
I modified the code a bit, but I'm not sure if it should work this way
fun launch(context: Context) = ActivityScreen (intentCreator = object: Creator<Context, Intent> {
override fun create(argument: Context): Intent {
return MainActivity.newIntent(context)
}
})
It works, but I think you can do it differently
Your lambda is getting its context externally rather than from a lambda argument, so its signature doesn't match the signature of your interface.
Try this:
ActivityScreen { LaunchActivity.newIntent(it) }
If it isn't able to infer the context from that, I guess you would need:
ActivityScreen { context: Context -> LaunchActivity.newIntent(context) }
I am trying to listen to my ViewModels MutableStateFlow from my FlutterSceneView. But I get the following error when trying to set the listener from the views init:
Suspend function 'listenToBackgroundColor' should be called only from a coroutine or another suspend function
class FlutterSceneView(context: Context, private val viewModel: FlutterSceneViewModelType): PlatformView {
private val context = context
private val sceneView = SceneView(context)
init {
listenToBackgroundColor() // Error here
}
private suspend fun listenToBackgroundColor() {
viewModel.colorFlow.collect {
val newColor = Color.parseColor(it)
sceneView.setBackgroundColor(newColor)
}
}
}
My ViewModel:
interface FlutterSceneViewModelType {
var colorFlow: MutableStateFlow<String>
}
class FlutterSceneViewModel(private val database: Database): FlutterSceneViewModelType, ViewModel() {
override var colorFlow = MutableStateFlow<String>("#FFFFFF")
init {
listenToBackgroundColorFlow()
}
private fun listenToBackgroundColorFlow() {
database.backgroundColorFlow.watch {
colorFlow.value = it.hex
}
}
}
the .watch call is a helper I have added so that this can be exposed to iOS using Kotlin multi-platform, it looks as follows but I can use collect instead if necessary:
fun <T> Flow<T>.asCommonFlow(): CommonFlow<T> = CommonFlow(this)
class CommonFlow<T>(private val origin: Flow<T>) : Flow<T> by origin {
fun watch(block: (T) -> Unit): Closeable {
val job = Job()
onEach {
block(it)
}.launchIn(CoroutineScope(Dispatchers.Main + job))
return object : Closeable {
override fun close() {
job.cancel()
}
}
}
}
I resolved this by using viewModel context:
private fun listenToBackgroundColor() {
viewModel.colorFlow.onEach {
val newColor = Color.parseColor(it)
sceneView.setBackgroundColor(newColor)
}.launchIn(viewModel.viewModelScope)
}
I had to import the following into my ViewModel:
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
from:
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0")
I'm writing Espresso UI test which mocks viewModel, referring GithubBrowserSample
what is the use of "TaskExecutorWithIdlingResourceRule", declaring Junit Rule will take care of IdlingResource?
Even after referring this "TaskExecutorWithIdlingResourceRule" class in my project whenever I build, compiler doesn't throw any error but when I run the test case it shows the Unresolved Error(s)
TaskExecutorWithIdlingResourceRule.kt
import androidx.arch.core.executor.testing.CountingTaskExecutorRule
import androidx.test.espresso.IdlingRegistry
import androidx.test.espresso.IdlingResource
import org.junit.runner.Description
import java.util.UUID
import java.util.concurrent.CopyOnWriteArrayList
import java.util.concurrent.TimeUnit
class TaskExecutorWithIdlingResourceRule : CountingTaskExecutorRule() {
// give it a unique id to workaround an espresso bug where you cannot register/unregister
// an idling resource w/ the same name.
private val id = UUID.randomUUID().toString()
private val idlingResource: IdlingResource = object : IdlingResource {
override fun getName(): String {
return "architecture components idling resource $id"
}
override fun isIdleNow(): Boolean {
return this#TaskExecutorWithIdlingResourceRule.isIdle
}
override fun registerIdleTransitionCallback(callback: IdlingResource.ResourceCallback) {
callbacks.add(callback)
}
}
private val callbacks = CopyOnWriteArrayList<IdlingResource.ResourceCallback>()
override fun starting(description: Description?) {
IdlingRegistry.getInstance().register(idlingResource)
super.starting(description)
}
override fun finished(description: Description?) {
drainTasks(10, TimeUnit.SECONDS)
callbacks.clear()
IdlingRegistry.getInstance().unregister(idlingResource)
super.finished(description)
}
override fun onIdle() {
super.onIdle()
for (callback in callbacks) {
callback.onTransitionToIdle()
}
}
}
Mocktest
#RunWith(AndroidJUnit4::class)
class MockTest {
#Rule
#JvmField
var activityRule = IntentsTestRule(SingleFragmentActivity::class.java, true, true)
#Rule
#JvmField
val executorRule = TaskExecutorWithIdlingResourceRule()
private lateinit var viewModel: SeriesFragmentViewModel
private val uiModelList = mutableListOf<SeriesBaseUIModel>()
private val seriesMutableLiveData = MutableLiveData<List<SeriesBaseUIModel>>()
private val seriesFragment = SeriesFragment()
#Before
fun init(){
viewModel = mock(SeriesFragmentViewModel::class.java)
`when`(viewModel.seriesLiveData).thenReturn(seriesMutableLiveData)
ViewModelUtil.createFor(viewModel)
activityRule.activity.setFragment(seriesFragment)
EspressoTestUtil.disableProgressBarAnimations(activityRule)
}
#Test
fun testLoading()
{
//Thread.sleep(3000)
uiModelList.add(ProgressUIModel())
seriesMutableLiveData.postValue(uiModelList.toList())
onView(withId(R.id.pod_series_recycler_view))
.check(selectedDescendantsMatch(withId(R.id.pod_adapter_series_header_title), isDisplayed()))
onView(withId(R.id.pod_series_recycler_view))
.check(selectedDescendantsMatch(withId(R.id.pod_adapter_series_header_title), withText(R.string.pod_series_header_title_text)))
onView(withId(R.id.pod_series_recycler_view))
.check(selectedDescendantsMatch(withId(R.id.pod_adapter_series_header_description), isDisplayed()))
onView(withId(R.id.pod_series_recycler_view))
.check(selectedDescendantsMatch(withId(R.id.pod_adapter_series_header_title), withText("Hello")))
Thread.sleep(5000)
}
}