I return a RxJava Single type as you see bellow :
private fun getSpeciesWrapper(
character: Character, getSpecieUseCase: GetSpecieUseCase, getPlanetUseCase: GetPlanetUseCase,
): Single<List<SpecieWrapper>> {
var name: String? = null
var language: String? = null
return Flowable.fromIterable(character.specieUrls)
.flatMapSingle { specieUrl -> getSpecieUseCase(specieUrl) }
.flatMapSingle { specie ->
name = specie.name
language = specie.language
getPlanetUseCase(specie.homeWorld)
}.map { planet ->
SpecieWrapper(name, language, planet.population)
}.toList()
}
As you see I have defined name and language variables in order to use them in the map section to create a list of Specie wrapper.
Is there any solution in RxJava2 that I could simplify the Single type by removing these local variables (name and language) ?
You can simply nest the map call inside of the flatMapSingle call:
private fun getSpeciesWrapper(
character: Character, getSpecieUseCase: GetSpecieUseCase, getPlanetUseCase: GetPlanetUseCase,
): Single<List<SpecieWrapper>> {
return Flowable.fromIterable(character.specieUrls)
.flatMapSingle { specieUrl -> getSpecieUseCase(specieUrl) }
.flatMapSingle { specie -> getPlanetUseCase(specie.homeWorld)
.map { planet ->
SpecieWrapper(specie.name, specie.language, planet.population)
}
}.toList()
}
fun updatePath(query: String?){
this.query = query
}
fun build(): String {
if (!query.isNullOrEmpty()) {
val encodedQuery = encode(query)
}
}
encode() accepts only not null String variable.
but here it still complains that query that I am passing?
I was expecting using isNullOrEmpty() should solve this problem.
You can use kotlin Scope function with Null Safety to avoid such warnings.
Like this -
fun build(): String {
query?.let {
if (it.isNotEmpty()) {
val encodedQuery = encode(query)
}
}
}
EDIT: Thanks #Tenfour04 for notifying this, we should used instance inside block.
fun build(): String {
query?.let { it ->
if (it.isNotEmpty()) {
val encodedQuery = encode(**it**)
}
}
}
I would use smartcast or possibly takeIf to avoid nesting.
query?.takeIf { it.isNotEmpty() }?.let {
encode(it)
}
If encode accepts any non-null string (including empty string), then you could do the following.
fun build(): String {
...
val encodedQuery = query?.let(::encode)
...
}
It is not possible for the compiler to smartcast, as your query is a var, therefore it might change. You could do the following:
val query = query
Before the if
This shadows the name query. You can still use the query property through this.query
I'm attempting to create a MutableList using kotlin but I'm getting an error stating:
Type inference failed. Expected type mismatch: inferred type is MutableList but MutableCollection was expected
...and I'm not sure how to convert the MutableList to a MutableCollection.
I've tried using:
.toMutableList().toCollection()
but it's looking for a destination - and I'm not sure what to do.
Code Snippet:
data class HrmSearchResult(
var rssi: Short?,
var adjustRssi: Short?,
var timeout: Int,
var serialNumber: Long?,
var isIn: Boolean,
var countIn: Int
)
private val hashMapHrm = ConcurrentHashMap<Long?, HrmSearchResult>()
val hrmDeviceList: MutableCollection<Long>
get() = try {
if (hashMapHrm.elements().toList().none { it.isIn}) {
//if there are no member in range, then return empty list
arrayListOf()
} else {
hashMapHrm.elements()
.toList()
.filter { it.isIn }
.sortedByDescending { it.adjustRssi }
.map { it.serialNumber }
.toMutableList().toCollection()
}
} catch (ex: Exception) {
AppLog.e(
LOG, "Problem when get devices " +
"return empty list: ${ex.localizedMessage}"
)
arrayListOf()
}
Any suggestions are appreciated.
The problem is the nullability, not the collection type, i.e. that you are creating a List<Long?> where a List<Long> is expected.
You can reproduce your error message (inferred type is MutableList<Long?> but MutableCollection<Long> was expected) minimally with this:
val foo: MutableCollection<Long> =
listOf(1L, 2, 3, 4, null)
.toMutableList()
And you can fix it by inserting .filterNotNull() to remove potential nulls, and convert a List<T?> to a List<T>:
val foo: MutableCollection<Long> =
listOf(1L, 2, 3, 4, null)
.filterNotNull()
.toMutableList()
(So your .toCollection() call is actually not needed and can be dropped)
Some other notes specific to your code:
You probably want to use .values over .elements.toList(), and map { }.filterNotNull() can be combined into mapNotNull, so in summary, you probably want to write your chain as
hashMapHrm.values
.filter { it.isIn }
.sortedByDescending { it.adjustRssi }
.mapNotNull { it.serialNumber }
.toMutableList()
Background: GSON, Kotlin, Retrofit
I am writing a restaurant app. In the home page, the user is able to load a list of restaurant brands. Each brand can have up to 3 cuisine types, the first one is non-null and the next two are nullable. Each cuisine type is within the CuisineType enum class.
What I would like to do is to create a joined string like this:
cuisineType1.title + cuisineType2?.title + cuisineType3?.title = combinedCuisines. This can make all cuisines shown within a textView in Chinese. In order to do this I created a helper class. In this helper class, if the CuisineType from Brand cannot map any of the enum members, it will display the raw name from the Brand JSON(incase of server error). I tried the three solutions commented out below and non of them work. Much help would be appreciated. Thanks in advance!
data class Brand(
#SerializedName("id")
val id: Int,
#SerializedName("name_en")
val nameEN: String?,
#SerializedName("cuisine_1")
val cuisineType1: String,
#SerializedName("cuisine_2")
val cuisineType2: String?,
#SerializedName("cuisine_3")
val cuisineType3: String?,
/*Solution 1(not working):
val combinedCuisines = CombineCuisineHelper.combineCuisines(cuisineType1, cuisineType2, cuisineType3)
***java.lang.IllegalArgumentException: Unable to create converter for class
*/
/*Solution 2(not working):
#Transient
val combinedCuisines = CombineCuisineHelper.combineCuisines(cuisineType1, cuisineType2, cuisineType3)
***combinedCuisines = null after network call in fragment
*/
) {
/* Solution 3(not working):
val combinedCuisines: String
get() = CombineCuisineHelper.combineCuisines(cuisineType1, cuisineType2, cuisineType3)
***problem with GSON, I can only map the #SerializedName from the Cuisine enum class and will only run the illegal argument solution from the CombineCuisineHelper. For example, get hong_kong_style from the JSON brand but it will not convert to HongKongStyle and map to its title.
*/
}
//It should be a long list but I shortened it.
enum class CuisineType {
#SerializedName("chinese")
Chinese,
#SerializedName("hong_kong_style")
HongKongStyle,
#SerializedName("cantonese")
Cantonese,
val title: Double
get() {
return when (this) {
Chinese -> "中菜"
HongKongStyle -> "港式"
Cantonese -> "粵式"
}
class CombineCuisineHelper {
companion object {
fun combineCuisines(cuisineSubtype1: String, cuisineSubtype2: String?, cuisineSubtype3: String?): String {
val combinedSubtypes = ArrayList<String?>()
combinedSubtypes += try {
CuisineSubtype.valueOf(cuisineSubtype1).title
} catch (e: IllegalArgumentException) {
cuisineSubtype1
}
if (cuisineSubtype2 != null) {
combinedSubtypes += try {
CuisineSubtype.valueOf(cuisineSubtype2).title
} catch (e: IllegalArgumentException) {
cuisineSubtype2
}
}
if (cuisineSubtype3 != null) {
combinedSubtypes += try {
CuisineSubtype.valueOf(cuisineSubtype3).title
} catch (e: IllegalArgumentException) {
cuisineSubtype3
}
}
}
The first and second solutions are not good, because the data might not be ready at the initialization time. The third solution is the one we can continue on:
val combinedCuisines: String
get() = CombineCuisineHelper.combineCuisines(cuisineType1, cuisineType2, cuisineType3)
The SerializedNames are useless for enum constants and won't work for you as you are expecting. So the valueOf method for enum won't find a value for literals like "hong_kong_style" and will throw an exception.
You can create your own helper method in your enum class like this:
enum class CuisineType {
Chinese,
HongKongStyle,
Cantonese;
val title: String
get() {
return when (this) {
Chinese -> "中菜"
HongKongStyle -> "港式"
Cantonese -> "粵式"
}
}
companion object {
//Note this helper method which manually maps string values to enum constants:
fun enumValue(title: String): CuisineType = when (title) {
"chinese" -> Chinese
"hong_kong_style" -> HongKongStyle
"cantonese" -> Cantonese
else -> throw IllegalArgumentException("Unknown cuisine type $title")
}
}
}
And then use this new method instead of the enum's own valueOf method:
val combinedSubtypes = ArrayList<String?>()
combinedSubtypes += try {
CuisineType.enumValue(cuisineSubtype1).title
} catch (e: IllegalArgumentException) {
cuisineSubtype1
}
//continued...
I have next use case: User comes to registration form, enters name, email and password and clicks on register button. After that system needs to check if email is taken or not and based on that show error message or create new user...
I am trying to do that using Room, ViewModel and LiveData. This is some project that on which I try to learn these components and I do not have remote api, I will store everything in local database
So I have these classes:
RegisterActivity
RegisterViewModel
User
UsersDAO
UsersRepository
UsersRegistrationService
So the idea that I have is that there will be listener attached to register button which will call RegisterViewModel::register() method.
class RegisterViewModel extends ViewModel {
//...
public void register() {
validationErrorMessage.setValue(null);
if(!validateInput())
return;
registrationService.performRegistration(name.get(), email.get(), password.get());
}
//...
}
So that is the basic idea, I also want for performRegistration to return to me newly created user.
The thing that bothers me the most is I do not know how to implement performRegistration function in the service
class UsersRegistrationService {
private UsersRepository usersRepo;
//...
public LiveData<RegistrationResponse<Parent>> performRegistration(String name, String email, String password) {
// 1. check if email exists using repository
// 2. if user exists return RegistrationResponse.error("Email is taken")
// 3. if user does not exists create new user and return RegistrationResponse(newUser)
}
}
As I understand, methods that are in UsersRepository should return LiveData because UsersDAO is returning LiveData
#Dao
abstract class UsersDAO {
#Query("SELECT * FROM users WHERE email = :email LIMIT 1")
abstract LiveData<User> getUserByEmail(String email);
}
class UsersRepository {
//...
public LiveData<User> findUserByEmail(String email) {
return this.usersDAO.getUserByEmail(email);
}
}
So my problem is how to implement performRegistration() function and how to pass value back to view model and then how to change activity from RegisterActivity to MainActivity...
You can use my helper method:
val profile = MutableLiveData<ProfileData>()
val user = MutableLiveData<CurrentUser>()
val title = profile.combineWith(user) { profile, user ->
"${profile.job} ${user.name}"
}
fun <T, K, R> LiveData<T>.combineWith(
liveData: LiveData<K>,
block: (T?, K?) -> R
): LiveData<R> {
val result = MediatorLiveData<R>()
result.addSource(this) {
result.value = block(this.value, liveData.value)
}
result.addSource(liveData) {
result.value = block(this.value, liveData.value)
}
return result
}
With the help of MediatorLiveData, you can combine results from multiple sources. Here an example of how would I combine two sources:
class CombinedLiveData<T, K, S>(source1: LiveData<T>, source2: LiveData<K>, private val combine: (data1: T?, data2: K?) -> S) : MediatorLiveData<S>() {
private var data1: T? = null
private var data2: K? = null
init {
super.addSource(source1) {
data1 = it
value = combine(data1, data2)
}
super.addSource(source2) {
data2 = it
value = combine(data1, data2)
}
}
override fun <S : Any?> addSource(source: LiveData<S>, onChanged: Observer<in S>) {
throw UnsupportedOperationException()
}
override fun <T : Any?> removeSource(toRemove: LiveData<T>) {
throw UnsupportedOperationException()
}
}
here is the gist for above, in case it is updated on the future:
https://gist.github.com/guness/0a96d80bc1fb969fa70a5448aa34c215
One approach is to use flows for this.
val profile = MutableLiveData<ProfileData>()
val user = MutableLiveData<CurrentUser>()
val titleFlow = profile.asFlow().combine(user.asFlow()){ profile, user ->
"${profile.job} ${user.name}"
}
And then your Fragment/Activity:
viewLifecycleOwner.lifecycleScope.launch {
viewModel.titleFlow.collectLatest { title ->
Log.d(">>", title)
}
}
One advantage to this approach is that titleFlow will only emit value when both live datas have emitted at least one value. This interactive diagram will help you understand this https://rxmarbles.com/#combineLatest
Alternative syntax:
val titleFlow = combine(profile.asFlow(), user.asFlow()){ profile, user ->
"${profile.job} ${user.name}"
}
Jose Alcérreca has probably the best answer for this:
fun blogpostBoilerplateExample(newUser: String): LiveData<UserDataResult> {
val liveData1 = userOnlineDataSource.getOnlineTime(newUser)
val liveData2 = userCheckinsDataSource.getCheckins(newUser)
val result = MediatorLiveData<UserDataResult>()
result.addSource(liveData1) { value ->
result.value = combineLatestData(liveData1, liveData2)
}
result.addSource(liveData2) { value ->
result.value = combineLatestData(liveData1, liveData2)
}
return result
}
without custom class
MediatorLiveData<Pair<Foo?, Bar?>>().apply {
addSource(fooLiveData) { value = it to value?.second }
addSource(barLiveData) { value = value?.first to it }
}.observe(this) { pair ->
// TODO
}
I did an approach based on #guness answer. I found that being limited to two LiveDatas was not good. What if we want to use 3? We need to create different classes for every case. So, I created a class that handles an unlimited amount of LiveDatas.
/**
* CombinedLiveData is a helper class to combine results from multiple LiveData sources.
* #param liveDatas Variable number of LiveData arguments.
* #param combine Function reference that will be used to combine all LiveData data results.
* #param R The type of data returned after combining all LiveData data.
* Usage:
* CombinedLiveData<SomeType>(
* getLiveData1(),
* getLiveData2(),
* ... ,
* getLiveDataN()
* ) { datas: List<Any?> ->
* // Use datas[0], datas[1], ..., datas[N] to return a SomeType value
* }
*/
class CombinedLiveData<R>(vararg liveDatas: LiveData<*>,
private val combine: (datas: List<Any?>) -> R) : MediatorLiveData<R>() {
private val datas: MutableList<Any?> = MutableList(liveDatas.size) { null }
init {
for(i in liveDatas.indices){
super.addSource(liveDatas[i]) {
datas[i] = it
value = combine(datas)
}
}
}
}
You can define a method that would combine multiple LiveDatas using a MediatorLiveData, then expose this combined result as a tuple.
public class CombinedLiveData2<A, B> extends MediatorLiveData<Pair<A, B>> {
private A a;
private B b;
public CombinedLiveData2(LiveData<A> ld1, LiveData<B> ld2) {
setValue(Pair.create(a, b));
addSource(ld1, (a) -> {
if(a != null) {
this.a = a;
}
setValue(Pair.create(a, b));
});
addSource(ld2, (b) -> {
if(b != null) {
this.b = b;
}
setValue(Pair.create(a, b));
});
}
}
If you need more values, then you can create a CombinedLiveData3<A,B,C> and expose a Triple<A,B,C> instead of the Pair, etc. Just like in https://stackoverflow.com/a/54292960/2413303 .
EDIT: hey look, I even made a library for you that does that from 2 arity up to 16: https://github.com/Zhuinden/livedata-combinetuple-kt
Many of these answers work, but also it is assumed the LiveData generic types are not-nullable.
But what if one or more of the given input types are nullable types (given the default Kotlin upper bound for generics is Any?, which is nullable)?
The result would be even though the LiveData emitter would emit a value (null), the MediatorLiveData will ignore it, thinking it's his own child live data value not being set.
This solution, instead, takes care of it by forcing the upper bound of the types passed to the mediator to be not null. Lazy but needed.
Also, this implementation avoids same-value after the combiner function has been called, which might or might not be what you need, so feel free to remove the equality check there.
fun <T1 : Any, T2 : Any, R> combineLatest(
liveData1: LiveData<T1>,
liveData2: LiveData<T2>,
combiner: (T1, T2) -> R,
): LiveData<R> = MediatorLiveData<R>().apply {
var first: T1? = null
var second: T2? = null
fun updateValueIfNeeded() {
value = combiner(
first ?: return,
second ?: return,
)?.takeIf { it != value } ?: return
}
addSource(liveData1) {
first = it
updateValueIfNeeded()
}
addSource(liveData2) {
second = it
updateValueIfNeeded()
}
}
LiveData liveData1 = ...;
LiveData liveData2 = ...;
MediatorLiveData liveDataMerger = new MediatorLiveData<>();
liveDataMerger.addSource(liveData1, value -> liveDataMerger.setValue(value));
liveDataMerger.addSource(liveData2, value -> liveDataMerger.setValue(value));
if you want both value not null
fun <T, V, R> LiveData<T>.combineWithNotNull(
liveData: LiveData<V>,
block: (T, V) -> R
): LiveData<R> {
val result = MediatorLiveData<R>()
result.addSource(this) {
this.value?.let { first ->
liveData.value?.let { second ->
result.value = block(first, second)
}
}
}
result.addSource(liveData) {
this.value?.let { first ->
liveData.value?.let { second ->
result.value = block(first, second)
}
}
}
return result
}
If you want to create a field and setup at construction time (use also):
val liveData1 = MutableLiveData(false)
val liveData2 = MutableLiveData(false)
// Return true if liveData1 && liveData2 are true
val liveDataCombined = MediatorLiveData<Boolean>().also {
// Initial value
it.value = false
// Observing changes
it.addSource(liveData1) { newValue ->
it.value = newValue && liveData2.value!!
}
it.addSource(selectedAddOn) { newValue ->
it.value = liveData1.value!! && newValue
}
}
Solved with LiveData extensions
fun <T, R> LiveData<T>.map(action: (t: T) -> R): LiveData<R> =
Transformations.map(this, action)
fun <T1, T2, R> LiveData<T1>.combine(
liveData: LiveData<T2>,
action: (t1: T1?, t2: T2?) -> R
): LiveData<R> =
MediatorLiveData<Pair<T1?, T2?>>().also { med ->
med.addSource(this) { med.value = it to med.value?.second }
med.addSource(liveData) { med.value = med.value?.first to it }
}.map { action(it.first, it.second) }
Java version, if anyone else is stuck working on some old project
var fullNameLiveData = LiveDataCombiner.combine(
nameLiveData,
surnameLiveData,
(name, surname) -> name + surname
)
public class LiveDataCombiner<First, Second, Combined> {
private First first;
private Second second;
private final MediatorLiveData<Combined> combined = new MediatorLiveData<>();
private final BiFunction<First, Second, Combined> combine;
public LiveData<Combined> getCombined() {
return combined;
}
public static <First, Second, Combined>LiveDataCombiner<First, Second, Combined> combine(
LiveData<First> firstData,
LiveData<Second> secondData,
BiFunction<First, Second, Combined> combine
) {
return new LiveDataCombiner<>(firstData, secondData, combine);
}
private LiveDataCombiner(
LiveData<First> firstData,
LiveData<Second> secondData,
BiFunction<First, Second, Combined> combine
) {
this.combine = combine;
addSource(firstData, value -> first = value);
addSource(secondData, value -> second = value);
}
private <T> void addSource(LiveData<T> source, Consumer<T> setValue) {
combined.addSource(source, second -> {
setValue.accept(second);
emit(combine());
});
}
private Combined combine() {
return combine.apply(first, second);
}
private void emit(Combined value) {
if (combined.getValue() != value)
combined.setValue(value);
}
}