Transform Mono<Void> in Mono<String> adding a value - reactive-programming

My program services offer some delete methods that return Mono<Void>, e.g.: fun delete(clientId: String) : Mono<Void>
After calling .delete("x") I would like to propagate the clientId downstream to do other operations:
userService.get(id).map{ user ->
userService.delete(user.id) //This returns Mono<Void>
.map {
user.id //Never called!!!
}
.map { userId ->
//other calls using the propagated userId
}
}
The problem is since delete returns a Mono<Void>, the following .map {
user.id } is never called. So how can I transform the Mono<Void> into a Mono<String> to propagate the userId?

You can use thenReturn operator:
userService.get(id)
.flatMap { user -> userService.delete(user.id).thenReturn(user.id) }
.flatMap { id -> //other calls using the propagated userId }

I managed to work around it using hasNext that transforms it into a Boolean:
#Test
fun `should`() {
val mono: Mono<String> = "1".toMono()
.flatMap { id ->
val map = Mono.empty<Void>()
.hasElement()
.map {
id + "a"
}.map {
(it + "1")
}
map
}
mono.doOnNext {
println(mono)
}.subscribe()
}

Related

Question about the conditional sentence of Observable. (RxSwift)

I tried to create a function runsample() that uses multiple observables as below.
If I meet a specific condition in the middle of the stream, I want to start from the beginning of function.
(foo1() in the example below)
In this case, how do I modify the runsample() function?
class SampleClass {
////////////////////////////////
// private
////////////////////////////////
private func foo1() -> Observable<String> {
// Do something
return .just("TEST")
}
private func foo2() -> Observable<Bool> {
// Do something
return .just(false) // or true
}
private func foo3() -> Observable<String> {
// Do something
return .just("Result")
}
////////////////////////////////
// public
////////////////////////////////
public func runSample() -> Observable<String> {
return Observable.just(())
.flatMap { [unowned self] _ in
self.foo1()
}
.flatMap { [unowned self] _ in
self.foo2()
}
// I want to retry foo1() when foo2() is false
// I want to make foo3() run only if foo2() is true.
.flatMap { [unowned self] _ in
self.foo3()
}
}
}
Based on your comment, this is what you want:
func runSample() -> Observable<String> {
struct NotValid: Error { }
return Observable.deferred {
foo1().flatMap { _ in
foo2().do(onNext: { isValid in
if !isValid { throw NotValid() }
})
}
}
.retry()
.flatMap { _ in foo3() }
}
It's a very strange requirement you have, but it's doable. I expect this is an X-Y problem though.
You really want to retry foo1()? That would imply that it failed but it obviously didn't. In any case, this will do what you want:
func runSample() -> Observable<String> {
foo1()
.flatMap { [foo2] _ in
foo2()
}
.flatMap { [foo1, foo3] isTrue in
isTrue ? foo3() : foo1()
}
}
This function will return an Observable. Every time that Observable is subscribed to, the first foo1() will be activated.
Every time the first foo1() emits a value, the value will be ignored (which is quite odd) and foo2() will be called. This will generate a new Observable which will be subscribed to.
Whenever any of the Observables generated by foo2() emit a value, if the value is true foo3() will be called, otherwise foo1() will be called. Whichever one is called, its Observable will be subscribed to.
The entire function will emit all the values that any foo1()s or foo3()s Observables emit.
Importantly for this example, you do not need to start with Observable.just(()).
Thinking about it, I'd prefer something like this:
func runSample() -> Observable<String> {
Observable.zip(foo1(), foo2())
.flatMap { $0.1 ? foo3() : .just($0.0) }
}
That way I don't have to run foo1() twice.

How I can update my adapter for RecyclerView after change my LiveData?

I created a fragment that in the onActivityCreated method fetches Firebase data by limiting the query to a calendar date. Then I place Observers on my LiveData that are inside my ViewModel and that will deliver the list to my Adapter.
If I add, remove or update items in the same list, the changes are sent to firebase and the adapter reflects them on the screen. It works ok.
But, I am trying to develop a filter button, which will basically change the deadline date for the Firebase query. When I select a particular filter, the viewModel needs to retrieve the data from Firebase limited to the filter date. This generates a new list, having a different size from the previous one.
However, when the query occurs, the Adapter's getItemCount() method stores the size of the last list. This fact confuses the Adapter and the functions notifyItemInserted and notifyItemRemoved end up making confusing animations on the screen after changing the filter. I dont know whats is wrong.
How can I correctly observes LiveData and tell the adapter? Am I making a mistake in the MVVM architecture or forgetting some function?
My Fragment:
class HistoryFragment : Fragment(), OnItemMenuRecyclerViewClickListener {
private lateinit var mSecurityPreferences: SecurityPreferences
private lateinit var viewModel: BalancesViewModel
private lateinit var adapter: BalancesAdapter
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
setHasOptionsMenu(true)
viewModel = ViewModelProvider(this).get(BalancesViewModel::class.java)
adapter = BalancesAdapter(requireContext())
mSecurityPreferences = SecurityPreferences(requireContext())
return inflater.inflate(R.layout.fragment_history, container, false)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
setupFilter()
//Setup adapter
adapter.listenerMenu = this
recycler_view_history.adapter = adapter
//Fetch data based in filter by date
viewModel.fetchBalances(mSecurityPreferences.getStoredLong(FILTER_DATE))
// Put logic to listen RealTimeUpdates
viewModel.getRealTimeUpdates(mSecurityPreferences.getStoredLong(FILTER_DATE))
viewModel.balances.observe(viewLifecycleOwner, Observer {
adapter.setBalances(it)
})
viewModel.balance.observe(viewLifecycleOwner, Observer {
adapter.addBalance(it)
})
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.history_menu_filter, menu)
super.onCreateOptionsMenu(menu, inflater)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.item_menu_filter_this_month -> {
updateFilter(THIS_MONTH)
}
R.id.item_menu_filter_two_months -> {
updateFilter(TWO_MONTHS)
}
R.id.item_menu_filter_last_six_months -> {
updateFilter(LAST_SIX_MONTHS)
}
R.id.item_menu_filter_all -> {
updateFilter(ALL_MONTHS)
}
}
return super.onOptionsItemSelected(item)
}
private fun setupFilter() {
var filterOption = mSecurityPreferences.getStoredLong(FILTER_DATE)
if (filterOption == 0L){
filterOption = HandleDate.getLongToFilter(LAST_SIX_MONTHS)
mSecurityPreferences.storeLong(FILTER_DATE, filterOption)
}
}
private fun updateFilter(filterOption: Int){
val newFilterOption = HandleDate.getLongToFilter(filterOption)
mSecurityPreferences.storeLong(FILTER_DATE, newFilterOption)
updateUI()
}
private fun updateUI(){
viewModel.fetchBalances(mSecurityPreferences.getStoredLong(FILTER_DATE))
viewModel.getRealTimeUpdates(mSecurityPreferences.getStoredLong(FILTER_DATE))
}
}
My ViewModel:
class BalancesViewModel : ViewModel() {
private val userReference = FirebaseAuth.getInstance().currentUser!!.uid
private val dbUserReference = FirebaseDatabase.getInstance().getReference(userReference)
private val _balances = MutableLiveData<List<Balance>>()
val balances: LiveData<List<Balance>>
get() = _balances
private val _balance = MutableLiveData<Balance>()
val balance: LiveData<Balance>
get() = _balance
private val _result = MutableLiveData<Exception?>()
val result: LiveData<Exception?>
get() = _result
fun addBalance(balance: Balance) {
balance.id = dbUserReference.push().key
dbUserReference.child(NODE_BALANCES).child(balance.id!!).setValue(balance)
.addOnCompleteListener {
if (it.isSuccessful) {
_result.value = null
} else {
_result.value = it.exception
}
}
}
private val childEventListener = object : ChildEventListener {
override fun onCancelled(error: DatabaseError) {
}
override fun onChildMoved(snapshot: DataSnapshot, p1: String?) {
}
override fun onChildChanged(snapshot: DataSnapshot, p1: String?) {
val balance = snapshot.getValue(Balance::class.java)
balance?.id = snapshot.key
_balance.value = balance
}
override fun onChildAdded(snapshot: DataSnapshot, p1: String?) {
val balance = snapshot.getValue(Balance::class.java)
balance?.id = snapshot.key
_balance.value = balance
}
override fun onChildRemoved(snapshot: DataSnapshot) {
val balance = snapshot.getValue(Balance::class.java)
balance?.id = snapshot.key
balance?.isDeleted = true
_balance.value = balance
}
}
fun getRealTimeUpdates(longLimitDate: Long) {
dbUserReference.child(NODE_BALANCES).orderByChild(COLUMN_DATE_MILLI)
.startAt(longLimitDate.toDouble()).addChildEventListener(childEventListener)
}
fun fetchBalances(longLimitDate: Long) {
dbUserReference.child(NODE_BALANCES).orderByChild(COLUMN_DATE_MILLI)
.startAt(longLimitDate.toDouble())
.addListenerForSingleValueEvent(object : ValueEventListener {
override fun onCancelled(error: DatabaseError) {}
override fun onDataChange(snapshot: DataSnapshot) {
if (snapshot.exists()) {
val listBalances = mutableListOf<Balance>()
for (balanceSnapshot in (snapshot.children)) {
val balance = balanceSnapshot.getValue(Balance::class.java)
balance?.id = balanceSnapshot.key
balance?.let { listBalances.add(it) }
}
listBalances.sortByDescending { it.dateMilli }
_balances.value = listBalances
}
}
})
}
fun updateBalance(balance: Balance) {
dbUserReference.child(NODE_BALANCES).child(balance.id!!).setValue(balance)
.addOnCompleteListener {
if (it.isSuccessful) {
_result.value = null
} else {
_result.value = it.exception
}
}
}
fun deleteBalance(balance: Balance) {
dbUserReference.child(NODE_BALANCES).child(balance.id!!).setValue(null)
.addOnCompleteListener {
if (it.isSuccessful) {
_result.value = null
} else {
_result.value = it.exception
}
}
}
My Adapter:
class BalancesAdapter(private val context: Context) :
RecyclerView.Adapter<BalancesAdapter.BalanceViewModel>() {
private var balances = mutableListOf<Balance>()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
BalanceViewModel(
LayoutInflater.from(parent.context)
.inflate(R.layout.item_recyclerview_balance, parent, false)
)
override fun getItemCount() = balances.size
override fun onBindViewHolder(holder: BalanceViewModel, position: Int) {
holder.view.text_view_value_balance_item.text = balances[position].value
holder.view.text_view_date_item.text = balances[position].date
}
fun setBalances(balances: List<Balance>) {
this.balances = balances as MutableList<Balance>
notifyDataSetChanged()
}
fun addBalance(balance: Balance) {
val index = balances.indexOf(balance)
if (!balances.contains(balance)) {
balances.add(balance)
notifyItemInserted(index)
} else {
if (balance.isDeleted) {
balances.removeAt(index)
notifyItemRemoved(index)
} else {
balances[index] = balance
}
}
notifyItemRangeChanged(index, itemCount)
}
class BalanceViewModel(val view: View) : RecyclerView.ViewHolder(view)
}
Tnks for your attention.
Okay, it's been 4 days since I asked this question and after feeling a little frustrated with the project I come back here on StackOverFlow to post my own answer.
The problematic issue within the code I showed is in my Adapter's addBalance method.
When I created the Balance data model, I set the isDeleted attribute to identify that it was deleted. Upon entering Firebase it receives a NULL value and therefore it ceases to exist.
Then, as I have two listeners (one defined in the addListenerForSingleValueEvent method and the other defined in the addChildEventListener method), one ends up triggering the other when there is a change in the Firebase data, but I don't want to go into detail on that issue. The fact is that I checked that my addBalance method was being called after I deleted an object, causing that object to be inserted back into the Adapter's data list, even before the removal operation ended in Firebase.
So I changed the logic of my method to make sure that my object was deleted and only included it in my Adapter list after checking the isDeleted attribute.
fun dealWithBalance(balance: Balance){
val index = balances.indexOf(balance)
if(balance.isDeleted && balances.contains(balance)){
balances.removeAt(index)
notifyItemRemoved(index)
} else if(!balance.isDeleted && !balances.contains(balance)){
balances.add(balance)
} else if(index >= 0){
balances[index] = balance
notifyItemChanged(index)
}
}
I renamed addBalance to dealWithBalance...

Combine scan with network request to update model in RxSwift

Currently I'm having a Observable created using scan to update underlying model using a PublishSubject like this:
class ViewModel {
private enum Action {
case updateName(String)
}
private let product: Observable<Product>
private let actions = PublishSubject<Action>()
init(initialProduct: Product) {
product = actions
.scan(initialProduct, accumulator: { (oldProduct, action) -> Product in
var newProduct = oldProduct
switch action {
case .updateName(let name):
newProduct.name = name
}
return newProduct
})
.startWith(initialProduct)
.share()
}
func updateProductName(_ name: String) {
actions.onNext(.updateName(name))
}
private func getProductDetail() {
/// This will call a network request
}
}
Every "local" actions like update product's name, prices... is done by using method like updateProductName(_ name: String) above. But what if I want to have a network request that also update the product, and can be called every time I want, for example after a button tap, or after calling updateProductName?
// UPDATE: After read iWheelBuy's comment and Daniel's answer, I ended up using 2 more actions
class ViewModel {
private enum Action {
case getDetail
case updateProduct(Product)
}
///....
init(initialProduct: Product) {
product = actions
.scan(initialProduct, accumulator: { (oldProduct, action) -> Product in
var newProduct = oldProduct
switch action {
case .updateName(let name):
newProduct.name = name
case .getDetail:
self.getProductDetail()
case .updateProduct(let p):
return p
}
return newProduct
})
.startWith(initialProduct)
.share()
}
func getProductDetail() {
actions.onNext(.getDetail)
}
private func getProductDetail(id: Int) {
ProductService.getProductDetail(id) { product in
self.actions.onNext(.updateProduct(product))
}
}
}
But I feel that, I trigger side effect (call network request) inside scan, without updating the model, is that something wrong?
Also how can I use a "rx" network request?
// What if I want to use this method instead of the one above,
// without subscribe inside viewmodel?
private func rxGetProductDetail(id: Int) -> Observable<Product> {
return ProductService.rxGetProductDetail(id: Int)
}
I'm not sure why #iWheelBuy didn't make a real answer because their comment is the correct answer. Given the hybrid approach to Rx in your question, I expect something like the below will accommodate your style:
class ViewModel {
private enum Action {
case updateName(String)
case updateProduct(Product)
}
private let product: Observable<Product>
private let actions = PublishSubject<Action>()
private var disposable: Disposable?
init(initialProduct: Product) {
product = actions
.scan(initialProduct, accumulator: { (oldProduct, action) -> Product in
var newProduct = oldProduct
switch action {
case .updateName(let name):
newProduct.name = name
case .updateProduct(let product):
newProduct = product
}
return newProduct
})
.startWith(initialProduct)
.share()
// without a subscribe, none of this matters. I assume you just didn't show all your code.
}
deinit {
disposable?.dispose()
}
func updateProductName(_ name: String) {
actions.onNext(.updateName(name))
}
private func getProductDetail() {
let request = URLRequest(url: URL(string: "https://foo.com")!)
disposable?.dispose()
disposable = URLSession.shared.rx.data(request: request)
.map { try JSONDecoder().decode(Product.self, from: $0) }
.map { Action.updateProduct($0) }
.subscribe(
onNext: { [actions] in actions.onNext($0) },
onError: { error in /* handle error */ }
)
}
}
The style above is still pretty imperative but if you don't want your use of Rx to leak out of the view model it's okay.
If you want to see a "full Rx" setup, you might find my sample repo interesting: https://github.com/danielt1263/RxEarthquake
UPDATE
But I feel that, I trigger side effect (call network request) inside scan, without updating the model, is that something wrong?
The scan function should be pure with no side effects. Calling a network request inside it's closure is inappropriate.

Kotlin equivalent of Swift Expectations/Promises

I am trying to write some UnitTests for my native mobile applications, but have ran into a roadblock in my Android Tests. Specifically, I am struggling to find an example of Kotlin's version of Swift's Expectations/Promises..
I've found examples of Kotlin Promises, but they seem to be way more complicated than needed...
For example, here is a test for the login API function in my iOS project:
func testLogin() {
/// Prepare for login
if CURRENT_USER != nil {
logout()
}
/// Login
let promise = expectation(description: "User is logged in.")
// 1. Given
var isSuccess: Bool = false
// 2. When
User.login(username: maleUsername, password: malePassword, success: {
isSuccess = true
promise.fulfill()
}) { (_, agreeToTerms) in
XCTFail()
}
wait(for: [promise], timeout: maxTimeOut)
// 3. Then
XCTAssertNotNil(CURRENT_USER)
XCTAssertTrue(isSuccess)
/// Logout
logout()
}
This is pretty simple to me. I have an asynchronous method login that has two possible completion blocks: success and failure; and I need to wait for one of them to complete before evaluating. To do this, I create a promise before the call, then I fulfill the promise in the two completion blocks, and I wait for the promise to fulfill before running my assertions.
Now in Kotlin, I have a similar test:
private val loginFragment = LoginFragment()
#Test
fun loginTest() {
val username = ""
val password = ""
// TODO: Create Promise
loginFragment.loginViewModel
.login(username, password)
.observe(loginFragment, Observer {
loginFragment.activity?.onResult(it?.result,
onSuccess = {
// TODO: Fill Promise
},
onValidationError = {
// TODO: Fail Test
})
})
// TODO: Assertions
}
But I can't find an equivalent of swift's promises..
Does one exist in Kotlin? If not, how would I implement a version of my Swift's testLogin method in Kotlin?
You can use Kotlin coroutines, for example:
#Test
fun loginTest() {
val result = runBlocking {
val loginResult = login()
loginResult
}
if (result == "Success") {
// do your work when Success
} else {
// do your work when Error
}
}
suspend fun login(): String = suspendCoroutine { continuation ->
val username = ""
val password = ""
loginFragment.loginViewModel
.login(username, password)
.observe(loginFragment, Observer {
loginFragment.activity?.onResult(it?.result,
onSuccess = {
continuation.resume("Success")
},
onValidationError = {
continuation.resume("Error") // take a look for other methods, e.g. resumeWithException(exception)
})
})
}
To use coroutines you need to add next lines to app's build.gradle file dependencies:
final KOTLIN_COROUTINES_VERSION = '1.0.1'
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$KOTLIN_COROUTINES_VERSION"
Hope it will help.
I found this Kotlin Promise lib closer to Swift Promises.
Using this library your test would like.
#Test
fun loginTest() {
val username = ""
val password = ""
Promise<Boolean, Exception> { promise ->
loginFragment.loginViewModel
.login(username, password)
.observe(loginFragment, Observer {
loginFragment.activity?.onResult(it?.result,
onSuccess = {
promise.resolve(true)
},
onValidationError = {
promise.reject(Exception("Login error"))
})
})
}.whenComplete {
when (it) {
is Promise.Result.Success -> {
assert(it.value)
}
}
}
}
I wrote in 20 minutes an implementation that looks like swift's expectation, but he is not sophisticated, use only for simple cases it does the work well.
typealias XCTestExceptionHandler = (KTTestException?) -> Unit
fun TestCase.expectation(description: String): KTTestExpectation {
return KTTestExpectation(description)
}
fun TestCase.waitFor(expectation: KTTestExpectation, timeout: Long, handler: XCTestExceptionHandler) {
expectation.handler = handler
Thread.sleep(timeout)
expectation.timedOut()
}
class KTTestExpectation(private val description: String) {
private var isFulfilled = false
var handler: XCTestExceptionHandler? = null
fun fulfill() {
Handler(Looper.getMainLooper()).postDelayed({
invokeHandlerWith(null)
}, 2)
}
fun timedOut() {
invokeHandlerWith(KTTestException("Timed out: $description"))
}
private fun invokeHandlerWith(error: KTTestException?) {
if (isFulfilled) return
isFulfilled = true
handler?.invoke(error)
error?.let { Assert.fail("Timed out: $description") }
}
}
class KTTestException(message:String): Exception(message)
Using:
fun testExpectation() {
var nb = 0
val expectation = expectation("Test")
MyAsyncFunc {
nb = 5
expectation.fulfill()
}
waitFor(expectation, 1000) { error ->
assertEquals(5, nb)
}
}
If anyone has the courage to convert the original code to Kotlin Here are the links:
https://github.com/apple/swift-corelibs-xctest/blob/main/Sources/XCTest/Public/Asynchronous/XCTWaiter.swift
https://github.com/apple/swift-corelibs-xctest/blob/main/Sources/XCTest/Public/Asynchronous/XCTestExpectation.swift

RxSwift - Concat only on condition

I have 2 stream which are being concatenated. If the first stream executes onError instead of onComplete, I shouldn't be concatenating the second stream.
example:
func updateEntity(entities: [Member]) -> Observable<[Result<Member>]> {
let remoteUpdate = remoteStore.update(entities: entities)
return remoteUpdate.concat(localStore.update(entities: entities))
}
I shouldn't be updating the localStore if remoteUpdate throws an error, onError is called in remoteUpdate
Update:
public override func update(entities: [PlaceAlert]) -> Observable<[Result<PlaceAlert>]> {
let remoteUpdate = remoteStore.update(entities: entities)
var entityPlaceHolder: [PlaceAlert] = entities
return remoteUpdate.catchError { _ in
entityPlaceHolder = []
return localStore.update(entities: entityPlaceHolder)
}.concat(localStore.update(entities: entityPlaceHolder))
}
Just tried improvising. would this make any difference? LocalUpdate with emptyArray does nothing if there is an error
concat is not an if/then statement. Both updates are getting called at the same scope, it's just that the result won't reflect the localStore.update response if the remoteStore errors.
catchError is what you want in this context:
func updateEntity(entities: [Member]) -> Observable<[Result<Member>]> {
let remoteUpdate = remoteStore.update(entities: entities)
return remoteUpdate.catchError { _ in
localStore.update(entities: entities)
}
}