Alternative way to check if a string contains multiple strings in Kotlin? - string

I currently have the following code
fun main() {
// Build.Fingerprint sample strings
val debug_fingerprint: String? = "Company/device/device:11/3526.4353/0064504902000:userdebug/com-d,dev-keys"
val dev_fingerprint: String? = "Company/device/device:11/526.4353/0064504902000:user/com-d,dev-keys"
val user_fingerprint: String? = "Company/device/device:11/526.4353/0064504902000:user/com-p,release-keys"
//Testing
val osVarient: String = user_fingerprint?.let {
when {
check(listOf("userdebug", "dev-keys"), it) -> "Userdebug"
check(listOf("user", "dev-keys"), it) -> "Userdevsigned"
check(listOf("user", "release-keys"), it) -> "User"
else -> "Unknown variant"
}
} ?: run {
"Unknown variant null"
}
print(osVarient)
}
fun check(args: List<String>,fingerprint: String): Boolean {
for(arg in args) {
if(!fingerprint.contains(arg)){
return false
}
}
return true
}
The above code works but I'm wondering if there is more elegant way of writing the code.
Are there any alternatives in Kotlin to compare multiple substrings to a string?

You may use a dictionary for OS variants.
Iterable<T>.all is a builtin kotlin stdlib function to check a predicate on all elements.
fun main() {
val userFingerprint: String? = "Company/device/device:11/526.4353/0064504902000:user/com-p,release-keys"
val variantMap = mapOf(
"Userdebug" to listOf("userdebug", "dev-keys"),
"Userdevsigned" to listOf("user", "dev-keys"),
"User" to listOf("user", "release-keys")
)
val osVariant = userFingerprint?.let { fingerprint ->
variantMap.entries
.firstOrNull { check(it.value, fingerprint) }
?.key
} ?: "Unknown variant null"
println(osVariant)
}
fun check(fingerprintTypes: List<String>, fingerprint: String): Boolean {
return fingerprintTypes.all { it in fingerprint }
// return fingerprintTypes.all { fingerprint.contains(it) }
}

Related

How can I convert a camel case string to snake case and back in idiomatic Kotlin?

Looking for code that will do conversions like this:
"MyCamelCaseA" to "my_camel_case_a"
"AMultiWordString" to "a_multi_word_string"
"my_camel_case_a" to "myCamelCaseA" or "MyCamelCaseA"
"a_multi_word_string" to "aMultiWordString" or "AMultiWordString"
Here are extensions to the String class that use regex and replacements to convert a string from camel case to snake case, and from snake case to camel case:
val camelRegex = "(?<=[a-zA-Z])[A-Z]".toRegex()
val snakeRegex = "_[a-zA-Z]".toRegex()
// String extensions
fun String.camelToSnakeCase(): String {
return camelRegex.replace(this) {
"_${it.value}"
}.toLowerCase()
}
fun String.snakeToLowerCamelCase(): String {
return snakeRegex.replace(this) {
it.value.replace("_","")
.toUpperCase()
}
}
fun String.snakeToUpperCamelCase(): String {
return this.snakeToLowerCamelCase().capitalize()
}
Here are examples using the String extension:
print("${"MyCamelCaseA".camelToSnakeCase()}\n")
my_camel_case_a
print("${"AMultiWordString".camelToSnakeCase()}\n")
a_multi_word_string
"my_camel_case_a".snakeToLowerCamelCase()
myCamelCaseA
"my_camel_case_a".snakeToUpperCamelCase()
MyCamelCaseA
Here's my stab at this.
fun String.camelToSnakeCase() = fold(StringBuilder(length)) { acc, c ->
if (c in 'A'..'Z') (if (acc.isNotEmpty()) acc.append('_') else acc).append(c + ('a' - 'A'))
else acc.append(c)
}.toString()
My approach is also written in the form of extension function, but it does not use regular expressions, instead going character-by-character, processing them and folding the processing result into the accumulator, which at the beginning is an empty StringBuilder. The processing is as follows:
if the character is not an upper-case Latin letter, add it to accumulator as is
if the character is an upper-case Latin letter, then also check if this is not the first character of the string (accumulator is not empty). If it is not, then add underscore to accumulator. Finally add lower-cased character.
One thing to note, is that kotlin.text.StringBuilder is used, not the JDK one.
I would go with these implementations:
fun String.toCamelCase() =
split('_').joinToString("", transform = String::capitalize)
... which splits the string using snakes as delimiters, and then reattaches the parts as capitalized words without a delimiter.
fun String.toSnakeCase() = replace(humps, "_").toLowerCase()
private val humps = "(?<=.)(?=\\p{Upper})".toRegex()
... which uses a regex to find the positions before humps, inserting snakes, and then converts the whole string to lowercase. The regex consists of two parts, the first one (?<=.) is a positive look-behind saying that it must be preceded by a character, and the second part (?=\\p{Upper}) is using a positive look-ahead saying it must be followed by an uppercase character.
If you have jackson-databind in your classpath, you can use the following utility function:
import com.fasterxml.jackson.databind.PropertyNamingStrategies
fun String.toSnakeCase(): String =
PropertyNamingStrategies.SnakeCaseStrategy().translate(this)
fun main() {
// should output this_is_the_case
println("thisIsTheCase".toSnakeCase())
}
this is my try with kotlin only
val camelCaseString = "thisIsCamelCase"
val snakeCaseString = camelCaseString.map {
if (it.isUpperCase()){
"_${it.toLowerCase()}"
}else
{"$it"}
}
.joinToString(separator = "")
System.out.println("here is your snake string: $snake_case_string")
here is your snake string: this_is_camel_case
convert from snake to camel
val snakeCaseString = "snake_case_string"
val camelCase = StringBuilder()
var prevChar = '$'
snakeCaseString.forEach {
if(prevChar.equals('_')){
camelCase.append(it.toUpperCase())
}else if(!it.equals('_')){
camelCase.append(it)
}
prevChar = it
}
System.out.println(camelCase.toString())
snakeCaseString
I took one of the answers here, added Title Case and changed the API a bit
val camelRegex = "(?<=[a-zA-Z])[A-Z]".toRegex()
val snakeRegex = "_[a-zA-Z]".toRegex()
#JvmInline
value class SnakeCaseString(private val string: String) {
fun toCamelCase(): String = snakeRegex.replace(string) { it.value.replace("_", "").uppercase() }
fun toUpperCamelCase(): String =
toCamelCase().replaceFirstChar { if (it.isLowerCase()) it.titlecase() else it.toString() }
fun toTitleCase(): String = snakeRegex.replace(string) { it.value.replace("_", " ").uppercase() }
.replaceFirstChar { if (it.isLowerCase()) it.titlecase() else it.toString() }
}
#JvmInline
value class CamelCaseString(private val string: String) {
fun toSnakeCase(): String = camelRegex.replace(string) { "_${it.value}" }.lowercase()
fun toTitleCase(): String = camelRegex.replace(string) { "_${it.value}" }
.replaceFirstChar { if (it.isLowerCase()) it.titlecase() else it.toString() }
}
fun String.asSnakeCase() = SnakeCaseString(this)
fun String.asCamelCase() = CamelCaseString(this)
If you want a method with an input and output, this is how I did it:
private fun convertCamelToSnakeCase(camelCase : String) : String {
val snakeCase = StringBuilder()
for(character in camelCase) {
if(character.isUpperCase()) {
snakeCase.append("_${character.toLowerCase()}")
} else {
snakeCase.append(character)
}
}
return snakeCase.removePrefix("_").toString()
}

Loading indicator does not hide if api failed to retrieve data although it hides if api succeed to retrieve data in Android Paging library

I have a remote server from where I want to fetch 20 items(Job) per api call and show them in RecyclerView using paging library.
For that, I want to show a loading indicator at the beginning of the first api call when list of items is being fetched from the server. Everything is okay if data is fetched successfully. That means the loading indicator got invisible if data loaded successfully. The code is given bellow.
JobService.KT
#GET(Constants.API_JOB_LIST)
fun getJobPost(
#Query("page") pageNumber: Int
): Observable<Response<JobResponse>>
JobResponse.kt
data class JobResponse(
#SerializedName("status") val status: Int? = null,
#SerializedName("message") val message: Any? = null,
#SerializedName("data") val jobData: JobData? = null
)
JobData.kt
data class JobData(
#SerializedName("jobs") val jobs: List<Job?>? = null,
#SerializedName("total") val totalJob: Int? = null,
#SerializedName("page") val currentPage: Int? = null,
#SerializedName("showing") val currentlyShowing: Int? = null,
#SerializedName("has_more") val hasMore: Boolean? = null
)
NetworkState.kt
sealed class NetworkState {
data class Progress(val isLoading: Boolean) : NetworkState()
data class Failure(val errorMessage: String?) : NetworkState()
companion object {
fun loading(isLoading: Boolean): NetworkState = Progress(isLoading)
fun failure(errorMessage: String?): NetworkState = Failure(errorMessage)
}
}
Event.kt
open class Event<out T>(private val content: T) {
private var hasBeenHandled = false
fun getContentIfNotHandled() = if (hasBeenHandled) {
null
} else {
hasBeenHandled = true
content
}
fun peekContent() = content
}
JobDataSource.kt
class JobDataSource(
private val jobService: JobService,
private val compositeDisposable: CompositeDisposable
) : PageKeyedDataSource<Int, Job>() {
val paginationState: MutableLiveData<Event<NetworkState>> = MutableLiveData()
val initialLoadingState: MutableLiveData<Event<NetworkState>> = MutableLiveData()
val totalJob: MutableLiveData<Event<Int>> = MutableLiveData()
companion object {
private const val FIRST_PAGE = 1
}
override fun loadInitial(params: LoadInitialParams<Int>, callback: LoadInitialCallback<Int, Job>) {
compositeDisposable += jobService.getJobPost(FIRST_PAGE)
.performOnBackgroundOutputOnMain()
.doOnSubscribe { initialLoadingState.postValue(Event(loading(true))) }
.doOnTerminate { initialLoadingState.postValue(Event(loading(false))) }
.subscribe({
if (it.isSuccessful) {
val jobData = it.body()?.jobData
totalJob.postValue(Event(jobData?.totalJob!!))
jobData.jobs?.let { jobs -> callback.onResult(jobs, null, FIRST_PAGE+1) }
} else {
val error = Gson().fromJson(it.errorBody()?.charStream(), ApiError::class.java)
when (it.code()) {
CUSTOM_STATUS_CODE -> initialLoadingState.postValue(Event(failure(error.message!!)))
else -> initialLoadingState.postValue(Event(failure("Something went wrong")))
}
}
}, {
if (it is IOException) {
initialLoadingState.postValue(Event(failure("Check Internet Connectivity")))
} else {
initialLoadingState.postValue(Event(failure("Json Parsing error")))
}
})
}
override fun loadAfter(params: LoadParams<Int>, callback: LoadCallback<Int, Job>) {
compositeDisposable += jobService.getJobPost(params.key)
.performOnBackgroundOutputOnMain()
.doOnSubscribe { if (params.key != 2) paginationState.postValue(Event(loading(true))) }
.doOnTerminate { paginationState.postValue(Event(loading(false))) }
.subscribe({
if (it.isSuccessful) {
val jobData = it.body()?.jobData
totalJob.postValue(Event(jobData?.totalJob!!))
jobData.jobs?.let { jobs -> callback.onResult(jobs, if (jobData.hasMore!!) params.key+1 else null) }
} else {
val error = Gson().fromJson(it.errorBody()?.charStream(), ApiError::class.java)
when (it.code()) {
CUSTOM_STATUS_CODE -> initialLoadingState.postValue(Event(failure(error.message!!)))
else -> initialLoadingState.postValue(Event(failure("Something went wrong")))
}
}
}, {
if (it is IOException) {
paginationState.postValue(Event(failure("Check Internet Connectivity")))
} else {
paginationState.postValue(Event(failure("Json Parsing error")))
}
})
}
override fun loadBefore(params: LoadParams<Int>, callback: LoadCallback<Int, Job>) {}
}
JobDataSourceFactory.kt
class JobDataSourceFactory(
private val jobService: JobService,
private val compositeDisposable: CompositeDisposable
): DataSource.Factory<Int, Job>() {
val jobDataSourceLiveData = MutableLiveData<JobDataSource>()
override fun create(): DataSource<Int, Job> {
val jobDataSource = JobDataSource(jobService, compositeDisposable)
jobDataSourceLiveData.postValue(jobDataSource)
return jobDataSource
}
}
JobBoardViewModel.kt
class JobBoardViewModel(
private val jobService: JobService
) : BaseViewModel() {
companion object {
private const val PAGE_SIZE = 20
private const val PREFETCH_DISTANCE = 20
}
private val jobDataSourceFactory: JobDataSourceFactory = JobDataSourceFactory(jobService, compositeDisposable)
var jobList: LiveData<PagedList<Job>>
init {
val config = PagedList.Config.Builder()
.setPageSize(PAGE_SIZE)
.setInitialLoadSizeHint(PAGE_SIZE)
.setPrefetchDistance(PREFETCH_DISTANCE)
.setEnablePlaceholders(false)
.build()
jobList = LivePagedListBuilder(jobDataSourceFactory, config).build()
}
fun getPaginationState(): LiveData<Event<NetworkState>> = Transformations.switchMap<JobDataSource, Event<NetworkState>>(
jobDataSourceFactory.jobDataSourceLiveData,
JobDataSource::paginationState
)
fun getInitialLoadingState(): LiveData<Event<NetworkState>> = Transformations.switchMap<JobDataSource, Event<NetworkState>>(
jobDataSourceFactory.jobDataSourceLiveData,
JobDataSource::initialLoadingState
)
fun getTotalJob(): LiveData<Event<Int>> = Transformations.switchMap<JobDataSource, Event<Int>>(
jobDataSourceFactory.jobDataSourceLiveData,
JobDataSource::totalJob
)
}
JobBoardFragment.kt
class JobBoardFragment : BaseFragment() {
private val viewModel: JobBoardViewModel by lazy {
getViewModel { JobBoardViewModel(ApiFactory.jobListApi) }
}
private val jobAdapter by lazy {
JobAdapter {
val bundle = Bundle()
bundle.putInt(CLICKED_JOB_ID, it.jobId!!)
navigateTo(R.id.jobBoard_to_jobView, R.id.home_navigation_fragment, bundle)
}
}
override fun getLayoutResId() = R.layout.fragment_job_board
override fun initWidget() {
job_list_recycler_view.adapter = jobAdapter
back_to_main_image_view.setOnClickListener { onBackPressed() }
}
override fun observeLiveData() {
with(viewModel) {
jobList.observe(this#JobBoardFragment, Observer {
jobAdapter.submitList(it)
})
getInitialLoadingState().observe(this#JobBoardFragment, Observer {
it.getContentIfNotHandled()?.let { state ->
when (state) {
is Progress -> {
if (state == loading(true)) {
network_loading_indicator.visible()
} else {
network_loading_indicator.visibilityGone()
}
}
is Failure -> context?.showToast(state.errorMessage.toString())
}
}
})
getPaginationState().observe(this#JobBoardFragment, Observer {
it.getContentIfNotHandled()?.let { state ->
when (state) {
is Progress -> {
if (state == loading(true)) {
pagination_loading_indicator.visible()
} else {
pagination_loading_indicator.visibilityGone()
}
}
is Failure -> context?.showToast(state.errorMessage.toString())
}
}
})
getTotalJob().observe(this#JobBoardFragment, Observer {
it.getContentIfNotHandled()?.let { state ->
job_board_text_view.visible()
with(profile_completed_image_view) {
visible()
text = state.toString()
}
}
})
}
}
}
But the problem is if data fetching failed due to internet connectivity or any other server related problem loading indicator does not invisible that means it still loading though I make the loadingStatus false and error message is shown. it means .doOnTerminate { initialLoadingState.postValue(Event(loading(false))) } is not called if error occured. This is the first problem. Another problem is loadInitial() and loadAfter() is being called simultaneously at the first call. But I just want the loadInitial() method is called at the beginning. after scrolling loadAfter() method will be called.
Try replacing all your LiveData's postValue() methods by setValue() or simply .value =.
The problem is that the postValue() method is for updating the value from a background thread to observers in the main thread. In this case you are always changing the values from the main thread itself, so you should use .value =.
Hope it's not too late.

Bridge channel to a sequence

This code is based on Coroutines guide example: Fan-out
val inputProducer = produce<String>(CommonPool) {
(0..inputArray.size).forEach {
send(inputArray[it])
}
}
val resultChannel = Channel<Result>(10)
repeat(threadCount) {
launch(CommonPool) {
inputProducer.consumeEach {
resultChannel.send(getResultFromData(it))
}
}
}
What is the right way to create a Sequence<Result> that will provide results?
You can get the channel .iterator() from the ReceiveChannel and then wrap that channel iterator into a Sequence<T>, implementing its normal Iterator<T> that blocks waiting for the result on each request:
fun <T> ReceiveChannel<T>.asSequence(context: CoroutineContext) =
Sequence {
val iterator = iterator()
object : AbstractIterator<T>() {
override fun computeNext() = runBlocking(context) {
if (!iterator.hasNext())
done() else
setNext(iterator.next())
}
}
}
val resultSequence = resultChannel.asSequence(CommonPool)
I had the same problem, and in the end I came up with this rather unusual/convoluted solution:
fun Channel<T>.asSequence() : Sequence<T> {
val itr = this.iterator()
return sequence<Int> {
while ( runBlocking {itr.hasNext()} ) yield( runBlocking<T> { itr.next() } )
}
}
I do not think it is particular efficient (go with the one provided by #hotkey), but it has a certain appeal to me at least.

Is it possible to mock accessors by Mockito in Kotlin?

Is it possible to mock getter and setter of the property by Mockito? Something like this:
#Test
fun three() {
val m = mock<Ddd>() {
// on { getQq() }.doReturn("mocked!")
}
assertEquals("mocked!", m.qq)
}
open class Ddd {
var qq : String = "start"
set(value) {
field = value + " by setter"
}
get() {
return field + " by getter"
}
}
To mock getter just write:
val m = mock<Ddd>()
`when`(m.qq).thenReturn("42")
also i suggest to use mockito-kotlin, to use useful extensions and functions like whenever:
val m = mock<Ddd>()
whenever(m.qq).thenReturn("42")
Complementing IRus' answer, you could also use the following syntax:
val mockedObj = mock<SomeClass> {
on { funA() } doReturn "valA"
on { funB() } doReturn "valB"
}
or
val mockedObj = mock<SomeClass> {
on(it.funA()).thenReturn("valA")
on(it.funB()).thenReturn("valB")
}

Creation of custom comparator for map in groovy

I have class in groovy
class WhsDBFile {
String name
String path
String svnUrl
String lastRevision
String lastMessage
String lastAuthor
}
and map object
def installFiles = [:]
that filled in loop by
WhsDBFile dbFile = new WhsDBFile()
installFiles[svnDiffStatus.getPath()] = dbFile
now i try to sort this with custom Comparator
Comparator<WhsDBFile> whsDBFileComparator = new Comparator<WhsDBFile>() {
#Override
int compare(WhsDBFile o1, WhsDBFile o2) {
if (FilenameUtils.getBaseName(o1.name) > FilenameUtils.getBaseName(o2.name)) {
return 1
} else if (FilenameUtils.getBaseName(o1.name) > FilenameUtils.getBaseName(o2.name)) {
return -1
}
return 0
}
}
installFiles.sort(whsDBFileComparator);
but get this error java.lang.String cannot be cast to WhsDBFile
Any idea how to fix this? I need to use custom comparator, cause it will be much more complex in the future.
p.s. full source of sample gradle task (description of WhsDBFile class is above):
project.task('sample') << {
def installFiles = [:]
WhsDBFile dbFile = new WhsDBFile()
installFiles['sample_path'] = dbFile
Comparator<WhsDBFile> whsDBFileComparator = new Comparator<WhsDBFile>() {
#Override
int compare(WhsDBFile o1, WhsDBFile o2) {
if (o1.name > o2.name) {
return 1
} else if (o1.name > o2.name) {
return -1
}
return 0
}
}
installFiles.sort(whsDBFileComparator);
}
You can try to sort the entrySet() :
def sortedEntries = installFiles.entrySet().sort { entry1, entry2 ->
entry1.value <=> entry2.value
}
you will have a collection of Map.Entry with this invocation. In order to have a map, you can then collectEntries() the result :
def sortedMap = installFiles.entrySet().sort { entry1, entry2 ->
...
}.collectEntries()
sort can also take a closure as parameter which coerces to a Comparator's compare() method as below. Usage of toUpper() method just mimics the implementation of FilenameUtils.getBaseName().
installFiles.sort { a, b ->
toUpper(a.value.name) <=> toUpper(b.value.name)
}
// Replicating implementation of FilenameUtils.getBaseName()
// This can be customized according to requirement
String toUpper(String a) {
a.toUpperCase()
}

Resources