I am writing a custom android lint to help to check if the private attributes match naming convention.
I used the test cases to verify my implementation. I used a method called isPrivateOrParameterInPrivateMethod() to check if it is private or not but it seems return true everytime I run it.
And I cannot find any documentation about this method (org.jetbrains.kotlin.asJava.classesisPrivateOrParameterInPrivateMethod). If I used it incorrectly, I would like to know.
Appreciate any comment or advice
class PrivateVariableMPrefixDetector : Detector(), Detector.UastScanner {
override fun getApplicableUastTypes() = listOf<Class<out UElement>>(UVariable::class.java)
override fun createUastHandler(context: JavaContext) =
NamingPatternHandler(context)
class NamingPatternHandler(private val context: JavaContext) : UElementHandler() {
override fun visitVariable(node: UVariable) {
node.takeIf { it.isPrivateOrParameterInPrivateMethod() }
?.takeUnless { node.name?.first()?.equals('m') ?: false }
?.run {
process(node, node)
}
}
private fun process(scope: UElement, declaration: PsiNamedElement) {
context.report(
ISSUE_PRIVATE_VAR_PREFIX_WITH_M,
scope,
context.getNameLocation(scope),
"${declaration.name} is not named with prefix m"
)
}
}
}
Test Case
#Test
fun should_not_warn_when_public_variable_is_not_stated_with_m_prefix() {
TestLintTask.lint()
.files(
TestFiles.kt(
"""
class Foo {
val binding
}
"""
).indented()
)
.issues(ISSUE_PRIVATE_VAR_PREFIX_WITH_M)
.run()
.expectClean()
}
Updated on 13/12/2020
There is a class JavaEvaluator inside the JavaContext, and I found some useful method to check the explicit modifier for the variable which suits my cases
class MyHandler(private val context: JavaContext) : UElementHandler() {
override fun visitField(node: UField) {
val isConstant = node.isFinal && node.isStatic
val isEnumConstant = node is UEnumConstant
if (!isConstant && !isEnumConstant) {
node.takeIf {
context.evaluator.hasModifiers(node, KtTokens.PRIVATE_KEYWORD)
}?.run {
process(node, node)
}
}
}
}
Outdated
After putting an effort on it, I found the following solution works. Although I still dun quite understand the meaning of node.sourcePsi, i will make it work first. Appreciate any advice or suggestion
node.takeIf { node.sourcePsi?.text?.startsWith("private") ?: false }
?.takeUnless { node.name.first() == 'm' && node.name.getOrNull(1)?.isUpperCase() ?: false }
?.run {
process(node, node)
}
Related
I am learning in State in jetpack compose. I found that State holders as source of truth. So created my some data can you guys guide me if I am doing wrong here.
PairViewModel.kt
class PairViewModel : ViewModel() {
var isBluetoothEnabled = mutableStateOf(false)
private set
fun onBluetoothEnable(value: Boolean) {
isBluetoothEnabled.value = value
}
}
PairScreen.kt
class PairScreenState(context: Context, viewModel: PairViewModel) {
private val bluetoothManager: BluetoothManager = context.getSystemService(BluetoothManager::class.java)
private val bluetoothAdapter: BluetoothAdapter by lazy {
bluetoothManager.adapter
}
init {
viewModel.onBluetoothEnable(bluetoothAdapter.isEnabled)
}
fun checkBluetoothStatus(bluetoothStatus: MutableState<Boolean>): BroadcastReceiver {
return object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
if (intent?.action == BluetoothAdapter.ACTION_STATE_CHANGED) {
when (intent.getIntExtra(
BluetoothAdapter.EXTRA_STATE,
BluetoothAdapter.ERROR
)) {
BluetoothAdapter.STATE_OFF -> {
bluetoothStatus.value = false
}
BluetoothAdapter.STATE_ON -> {
bluetoothStatus.value = true
}
}
}
}
}
}
}
#Composable
fun rememberPairScreenState(
context: Context,
viewModel: PairViewModel
) = remember {
PairScreenState(context, viewModel)
}
#Composable
fun PairContent(
context: Context = LocalContext.current,
viewModel: PairViewModel = getViewModel(),
rememberPairScreenState: PairScreenState = rememberPairScreenState(context, viewModel),
) {
AnimatedVisibility(visible = true) {
AppBarScaffold() {
Column(
modifier = Modifier
.fillMaxSize()
.verticalScroll(rememberScrollState())
) {
rememberPairScreenState.checkBluetoothStatus(viewModel.isBluetoothEnabled).apply {
context.registerReceiver(this, IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED))
}
if (viewModel.isBluetoothEnabled.value) {
println(">> Enable >>>")
} else {
println(">> Disable >>>")
}
}
}
}
}
#Preview(showBackground = true)
#Composable
fun PairContentPreview() {
PairContent()
}
I am using Bluetooth as example to understand state holder in my use case. Please guide me if you find anything wrong in my code. Thanks
Ill try my best here, I get where you're coming from, having a code that it's hard to verify if its the proper way of doing "yet", regardless of how many source materials you review like in github, sometimes references just doesn't exist yet right?
For State hoisting/handling, its good to follow the principles coming from the community. So the way I handle State Hoisting, is thinking of its purpose
So if its just something that needs to be local within the #Composable
remember {...}
If its something that deals with multiple logic and values, State class
class PersonState(val personParam: Person) {
.....
}
#Composable
fun rememberPersonState(person: Person) = remember(key1= person) {
PersonState(person)
}
If its something that deals with repository, network calls, use-cases where persistence is a major part of the requirement, ViewModel, and lifecyle is something you have to be aware of. ViewModel
class PersonScreenViewModel {
/..RepositoryStateFlows../
/..Data structural updates../
}
So far this mindset and approach helped me a bit when deciding how would I hoist my states.
As for your PairScreenState, consider this use-case solution coming from this post Detect if Soft Keyboard is Open or Close, where you can detect if the keyboard is open or not
I would have your BlueTooth usecase where I would implement it as a Composable utility function and returns a State where I can define a DisposableEffect, though this code is not working but I think you'll get my point here.
enum class BlueTooth {
ON, OFF
}
#Composable
fun BlueToothAsState(): State<BlueTooth> {
val blueToothState = remember { mutableStateOf(BlueTooth.OFF) }
DisposableEffect(view) {
var mReceiver : BroadcastReceiver? = object : BroadcastReceiver() {
/.../
when (intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR)) {
BluetoothAdapter.STATE_OFF -> {
blueToothState = BlueTooth.OFF
}
BluetoothAdapter.STATE_ON -> {
blueToothState = BlueTooth.ON
}
}
}
}
onDispose {
mReceiver = null
}
}
return blueToothState
}
As for the other parts of the code, I don't think you need it here if its just always set to true
AnimatedVisibility(visible = true)
I'm trying to insert separators to my list using the paging 3 compose library however, insertSeparators doesn't seem to indicate when we are at the beginning or end. My expectations are that before will be null at the beginning while after will be null at the end of the list. But it's never null thus hard to know when we are at the beginning or end. Here is the code:
private val filterPreferences =
MutableStateFlow(HomePreferences.FilterPreferences())
val games: Flow<PagingData<GameModel>> = filterPreferences.flatMapLatest {
useCase.execute(it)
}.map { pagingData ->
pagingData.map { GameModel.GameItem(it) }
}.map {
it.insertSeparators {before,after->
if (after == null) {
return#insertSeparators null
}
if (before == null) {
Log.i(TAG, "before is null: ") // never reach here
return#insertSeparators GameModel.SeparatorItem("title")
}
if(condition) {
GameModel.SeparatorItem("title")
}
else null
}
}
.cachedIn(viewModelScope)
GamesUseCase
class GamesUseCase #Inject constructor(
private val executionThread: PostExecutionThread,
private val repo: GamesRepo,
) : FlowUseCase<HomePreferences, PagingData<Game>>() {
override val dispatcher: CoroutineDispatcher
get() = executionThread.io
override fun execute(params: HomePreferences?): Flow<PagingData<Game>> {
val preferences = params as HomePreferences.FilterPreferences
preferences.apply {
return repo.fetchGames(query,
parentPlatforms,
platforms,
stores,
developers,
genres,
tags)
}
}
}
FlowUseCase
abstract class FlowUseCase<in Params, out T>() {
abstract val dispatcher: CoroutineDispatcher
abstract fun execute(params: Params? = null): Flow<T>
operator fun invoke(params: Params? = null) = execute(params).flowOn(dispatcher)
}
Here is the dependency :
object Pagination {
object Version {
const val pagingCompose = "1.0.0-alpha14"
}
const val pagingCompose = "androidx.paging:paging-compose:${Version.pagingCompose}"
}
I'm assuming that filterPreferences gives you Flow of some preference and useCase.execute returns Flow<PagingData<Model>>, correct?
I believe that the problem is in usage of flatMapLatest - it mixes page events of multiple useCase.execute calls together.
You should do something like this:
val games: Flow<Flow<PagingData<GameModel>>> = filterPreferences.mapLatest {
useCase.execute(it)
}.mapLatest {
it.map { pagingData -> pagingData.map { GameModel.GameItem(it) } }
}.mapLatest {
it.map { pagingData ->
pagingData.insertSeparators { before, after -> ... }
} // .cachedIn(viewModelScope)
}
This same structure works for us very well. I'm only not sure how cachedIn will work here, we are using a different caching mechanism, but you can try.
My project has a lot of operations that must be performed one after another. I was using listeners, but I found this tutorial Kotlin coroutines on Android and I wanted to change my sever call with better readable code. But I think I am missing something. The below code always return an error from getTime1() function:
suspend fun getTimeFromServer1() :ResultServer<Long> {
val userId = SharedPrefsHelper.getClientId()
return withContext(Dispatchers.IO) {
val call: Call<ResponseFromServer>? = userId?.let { apiInterface.getTime(it) }
(call?.execute()?.body())?.run {
val time:Long? = this.data?.time
time?.let {
Timber.tag("xxx").e("time received it ${it}")// I am getting the right result here
ResultServer.Success(it)
}
Timber.tag("xxx").e("time received ${time}")
}
ResultServer.Error(Exception("Cannot get time"))
}
}
fun getTime1() {
GlobalScope.launch {
when (val expr: ResultServer<Long> = NetworkLayer.getTimeFromServer1()) {
is ResultServer.Success<Long> -> Timber.tag("xxx").e("time is ${expr.data}")
is ResultServer.Error -> Timber.tag("xxx").e("time Error") //I am always get here
}}
}
}
But if I am using listeners (getTime()) everything works perfectly:
suspend fun getTimeFromServer(savingFinishedListener: SavingFinishedListener<Long>) {
val userId = SharedPrefsHelper.getClientId()
withContext(Dispatchers.IO) {
val call: Call<ResponseFromServer>? = userId?.let { apiInterface.getTime(it) }
(call?.execute()?.body())?.run {
val time:Long? = this.data?.time
time?.let {
Timber.tag("xxx").e("time received it ${it}")
savingFinishedListener.onSuccess(it)
}
}
savingFinishedListener.onSuccess(null)
}
}
fun getTime() {
GlobalScope.launch {
NetworkLayer.getTimeFromServer(object:SavingFinishedListener<Long>{
override fun onSuccess(t: Long?) {
t?.let {
Timber.tag("xxx").e("time here $it") //I am getting the right result
}
}
})
}
}
Thanks in advance for any help.
The last line of a lambda is implicitly the return value of that lambda. Since you don't have any explicit return statements in your withContext lambda, its last line:
ResultServer.Error(Exception("Cannot get time"))
means that it always returns this Error. You can put return#withContext right before your ResultServer.Success(it) to make that line of code also return from the lambda.
Side note: don't use GlobalScope.
I have a code in my repository which has to call two endpoints. I have used Flowable.zip() but it doesn't seem to return a value. The Call doesn't fail even if there is no network available.
fun fetchRateRemote(): Flowable<ResultWrapper<List<RateModel>>> {
return Flowable.zip<Flowable<CurrenciesDTO>, Flowable<RateDTO>, ResultWrapper<List<RateModel>>>(
{
apiEndpoints.fetchCurrencies(key)
}, {
apiEndpoints.fetchRate(key)
}, { t1, t2 ->
val rateList = mutableListOf<RateModel>()
t2.subscribe { rate->
for((k,v) in rate.quotes ){
val currency = k.removeRange(0,3)
t1.subscribe {cur->
val currencyName = cur.currencies[currency]
if (currencyName != null) {
rateList.add(RateModel("$currencyName ($currency)", v.toString()))
}
}
}
}
ResultWrapper.Success(rateList)
}).subscribeOn(Schedulers.io())
}
I use a wrapper to mimic state and this is what I do in my viewmodel.
private fun fetchRates(){
disposable.add(repository.fetchRateRemote()
.startWith(ResultWrapper.Loading)
.onErrorReturn {
ResultWrapper.Error(it)
}
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(object : DisposableSubscriber<ResultWrapper<List<RateModel>>>() {
override fun onComplete() {}
override fun onNext(rate: ResultWrapper<List<RateModel>>) {
rates.postValue(rate)
}
override fun onError(error: Throwable) {
error.printStackTrace()
}
})
)
}
I then observe rate in my activity via LiveData. The wrapper or the observation isn't the issue. It works with other calls, I do not know why the zip call doesn't work. I'm fairly new to RxJava so If I didn't implement something correctly in my repository please help correct me.
Okay! I made a lot of mistakes with the code in the repository above but I managed to fix it. Here's the solution. The Type arguments for the zip method was wrong! I didn't call the BiFunction argument properly too.
fun fetchRateRemote(): Flowable<ResultWrapper<List<RateModel>>> {
return Flowable.zip<CurrenciesDTO, RateDTO, ResultWrapper<List<RateModel>>>(
apiEndpoints.fetchCurrencies(key), apiEndpoints.fetchRate(key), BiFunction { t1, t2 ->
val rateList = mutableListOf<RateModel>()
for((k,v) in t2.quotes ){
val currencyCode = k.removeRange(0,3)
val currencyName = t1.currencies[currencyCode]
if (currencyName != null) {
rateList.add(RateModel("$currencyName ($currencyCode)", v.toString()))
}
}
ResultWrapper.Success(rateList)
}).subscribeOn(Schedulers.io())
}
Is there anything similar in Kotlin that provides same ability as the Swift keyword 'defer' ?
What the defer key word does is, it ensure that the code inside a defer block get executed before returning from a function.
Below is an example imagining that defer keyword existed in Kotlin.
class MyClass {
var timeStamp = 0L
fun isEdible(fruit: Fruit): Boolean {
defer {
timeStamp = System.currentTimeMillis()
}
if (fruit.isExpired) {
return false
}
if (fruit.isRipe) {
return true
}
return false
}
}
In the case above, regardless of at what point the function returns, the block inside defer will get executed and timestamp's value will get updated, just before the function ends.
I know Java there is the finally {} keyword used along with try{} catch{}, but it's is not exactly what defer offers.
There's no such keyword in Kotlin, but you can make a construct yourself that will work quite similarly. Something like this (note that this does not handle exceptions in the deferred blocks):
class Deferrable {
private val actions: MutableList<() -> Unit> = mutableListOf()
fun defer(f: () -> Unit) {
actions.add(f)
}
fun execute() {
actions.forEach { it() }
}
}
fun <T> defer(f: (Deferrable) -> T): T {
val deferrable = Deferrable()
try {
return f(deferrable)
} finally {
deferrable.execute()
}
}
Then you can use it like this:
class MyClass {
var timeStamp = 0L
fun isEdible(fruit: Fruit): Boolean = defer { d ->
d.defer {
timeStamp = System.currentTimeMillis()
}
if (fruit.isExpired) {
return false
}
if (fruit.isRipe) {
return true
}
return false
}
}
The closest equivalent is try/finally. catch is not necessary if there's no exceptions thrown.
try {
println("do something")
// ... the rest of your method body here
}
finally {
println("Don't forget about me!");
}
In Swift, defer is usually used to ensure you don't forget to clean up some kind of resource or another (file handle, database connection, shared memory map, etc.). For this purpose, Kotlin use with, which takes a closure, to which the resource is passed as an argument. The resource is valid for the lifetime of the closure, and is automatically closed at the end.
FileWriter("test.txt")
.use { it.write("something") }
// File is closed by now
Solution with exception handling:
class DeferContext {
private val list = mutableListOf<() -> Unit>()
fun defer(payload: () -> Unit) {
list += payload
}
/** lombok `#Cleanup` analog */
fun AutoCloseable.deferClose() = apply {
defer { close() }
}
fun executeDeferred(blockError: Throwable?) {
var error: Throwable? = blockError
for (element in list.reversed()) {
try {
element()
} catch (e: Throwable) {
if (error == null) {
error = e
} else {
error.addSuppressed(e)
}
}
}
error?.let { throw it }
}
}
inline fun <T> deferBlock(payload: DeferContext.() -> T): T {
val context = DeferContext()
var error: Throwable? = null
var result: T? = null
try {
result = context.payload()
} catch (e: Throwable) {
error = e
} finally {
context.executeDeferred(error)
}
return result as T
}
IMHO, main point of defer functionality is execution of deferred actions regardless of previously thrown exceptions.
usage:
deferBlock {
defer { println("block exited") }
val stream = FileInputStream("/tmp/a").deferClose()
}
I came across the same question today.
While I think the answer provided by marstran is good, I decided to refactor it a little bit.
fun <T> deferred(f: ((() -> Unit) -> Unit) -> T): T {
val actions: MutableList<() -> Unit> = mutableListOf()
try {
return f(actions::add)
} finally {
actions.asReversed().forEach { it() }
}
}
I got rid of the Deferrable class by using the list directly in the deffered function. This also solves the fact that the whole Deferrable object was passed to the calling code needing to call it.defer/d.defer. In this version the add method of the mutable list is directly passed into the lambda allowing to have a code that is closer to its go/swift version.
To address the suggestion given by mvndaai to use Stack I decided to call .asReversed() on the list. Maybe there is a LI-FO type in kotlin that is also available in non JVM variants, but if not I think this is a good solution.
the given sample would look like:
class MyClass {
var timeStamp = 0L
fun isEdible(fruit: Fruit): Boolean = deferred { defer ->
defer {
timeStamp = System.currentTimeMillis()
}
if (fruit.isExpired) {
return false
}
if (fruit.isRipe) {
return true
}
return false
}
}
If the class is Closeable you can use use block:
class MyClass : Closeable {
var timeStamp = 0L
override fun close() {
timeStamp = System.currentTimeMillis()
}
fun test(): Boolean {
this.use {
if (fruit.isExpired) {
return false
}
if (fruit.isRipe) {
return true
}
return false
}
}
}