requireActivity instead of requireContext in fragment - android-livedata

I am having a LiveData observer inside fragment, sometimes the code inside Observer{} throws error
Exception: java.lang.IllegalStateException: Fragment not attached to an activity.
at androidx.fragment.app.Fragment.requireActivity(Fragment.java:833)
It is happening because requireActivity() is returning null so the exception
myViewModel.saveData(data).observe(requireActivity(), Observer {
it?.let { response->
when(response.status){
Status.SUCCESS -> {
Toast.makeText(requireActivity(),"SUCCESS",Toast.LENGTH_LONG).show()
}
Status.ERROR -> {
Toast.makeText(requireActivity(),"ERROR",Toast.LENGTH_LONG).show()
}
Status.LOADING -> {
}
}
}
})
I confirmed there is no scenario that my fragment being detached from the activity.
I am suspecting the crash might be happening because I am not passing lifecycleowner reference to Observer?

Use ViewLifecycleOwner as LifecycleOwner while observing livedata from the fragment. Because the ViewLifecycleOwner is tied to the fragment's view lifecycle, but requireActivity() is tied to the fragment's overall lifecycle.
myViewModel.saveData(data).observe(viewLifecycleOwner) {
it?.let { response->
when(response.status){
Status.SUCCESS -> {
Toast.makeText(requireActivity(),"SUCCESS",Toast.LENGTH_LONG).show()
}
Status.ERROR -> {
Toast.makeText(requireActivity(),"ERROR",Toast.LENGTH_LONG).show()
}
Status.LOADING -> {
}
}
}
}

Related

How can I wrap an async function with an Observable

I have an async function that currently looks something like this
func startLoginFlow() {
IdentityProvider.shared.login { success, error in
// on success a user has completed authentication
if success {
delegate?.userIsAuthenticated()
}
// on error something wen't wrong
....
}
}
Essentially on success a delegate method is called and some action takes place as a result.
I'd like to wrap this as an observable instead. I do not have the option refactoring IdentityProvider.shared.login.
I essentially just need the observable to emit so I can subscribe and take action elsewhere using onNext.
I am currently doing the following
func startLoginFlow() -> Observable<Void> {
return Observable.create { [weak self] observable in
IdentityProvider.shared.login { success, error in
if success {
observable.onNext(Void())
}
}
return Disposables.create()
}
}
Is this the best way to do this? I wasn't sure if I should use Observable.of and subscribe to the result of IdentityProvider.shared.login
This is how I create Observables as well. The only thing I would note is to add in the errors so you can handle your observables when it errors out, and the completion, as well, to signal that your observable is complete.
func startLoginFlow() -> Observable<Void> {
return Observable.create { [weak self] observable in
IdentityProvider.shared.login { success, error in
if success {
observable.onNext(())
observable.onCompleted()
} else {
observable.onError(error)
}
}
return Disposables.create()
}
}
Observable.of's work in this case as well. It just emits the completed method. You can test this out yourself, if you were trying to create an Observable<String>, with both methods.
I find that doing Observable.create is beneficial here as you're doing network requests and that you can control how you want your observables to error, fail, or be completed.
Someone here gave a pretty good example as well:
Rxswift What difference between Observable.of and Observable<String>.create

Run 3 observables sequentially, using result from first in the last one

CONTEXT
I would like to run 3 different operations sequentially using RxSwift:
Fetch products
When products fetching is done, delete cache
When cache delete is done, save new cache with products from step 1
These are the function definitions in my services:
struct MyService {
static func fetchProducts() -> Observable<[Product]> {...}
static func deleteCache() -> Observable<Void> {...}
static func saveCache(_ products: [Product]) -> Observable<Void> {...}
}
I implement that behavior usually with flatMapLatest.
However, I will lose the result of the 1st observable ([Product]) with that approach, because the operation in the middle (deleteCache) doesn't receive arguments and returns Void when completed.
struct CacheViewModel {
static func refreshCache() -> Observable<Void> {
return MyService.fetchProducts()
.flatMapLatest { lostProducts in MyService.deleteCache() }
.flatMapLatest { MyService.saveCache($0) } // Compile error*
}
// * Cannot convert value of type 'Void' to expected argument type '[Product]'
}
The compile error is absolutely fair, since the operation in the middle 'breaks' the passing chain for the first result.
QUESTION
What mechanism is out there to achieve this serial execution with RxSwift, accumulating results of previous operations?
service
.fetchProducts()
.flatMap { products in
return service
.deleteCache()
.flatMap {
return service
.saveCache(products)
}
}
The easiest solution would be, just to return a new Observable of type Observable<Products> using the static method in the Rx framework just within the second flatMap(), passing in the lostProducts you captured in the flatmap-closure, i.e.:
static func refreshCache() -> Observable<Void> {
return MyService.fetchProducts()
.flatMapLatest { lostProducts -> Observable<[Product]> in
MyService.deleteCache()
return Observable.just(lostProducts)
}
.flatMapLatest { MyService.saveCache($0) } // No compile error
}
That way you are not losing the result of the first call in the flatMap, but just pass it through after having cleared the cache.
you can use do(onNext:) for deleting the cache data and then in flatMapLatest you can save the products. Optionally SaveCache and DeleteCache should return Completable so that you can handle error if the save or delete operation failed.
struct CacheViewModel {
static func refreshCache() -> Observable<Void> {
return MyService.fetchProducts()
.do(onNext: { _ in
MyService.deleteCache()
}).flatMap { products in
MyService.saveCache(products)
}
}
}

How to use swift-4 Promises then, done, catch and other blocks

I would like to learn about promises in swift-4. How to use multiple then statements and done, catch blocks.
Here I am trying to get the value from the promise. But I'm getting errors. Could someone help me to understand promises?
Here is my code.
import UIKit
import PromiseKit
struct User {
var firstname : String?
var lastname : String?
}
struct APIError {
var message : String?
}
class ViewController : UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let userPromise : Promise = self.getUserDetails()
userPromise.then { user -> Void in
//print(user.f)
}
}
func getUserDetails()->Promise<User> {
return Promise<User> { resolve in
let user = User(firstname: "Scot", lastname: "dem")
if ((user.firstname?.count) != nil) {
resolve.fulfill(user)
} else {
let error = APIError(message: "User not valid")
resolve.reject(error as! Error)
}
}
}
}
Once I get the user details I want to make a full name, uppercase promises which are dependent on userPromise.
I would like to use multiple then, done, finally blocks. Just want to understand usage.
Why I'm getting an error here when we use userPromise.then { user -> Void in
what should I give inside the block
In PromiseKit 6, then can no longer return Void. This is mainly due to the tuplegate issue in Swift 4.
Quote from PromieKit 6 Release News
With PromiseKit our then did multiple things, and we relied on Swift
to infer the correct then from context. However with multiple line
thens it would fail to do this, and instead of telling you that the
situation was ambiguous it would invent some other error. Often the
dreaded cannot convert T to AnyPromise. We have a troubleshooting
guide to combat this but I believe in tools that just work, and when
you spend 4 years waiting for Swift to fix the issue and Swift doesn’t
fix the issue, what do you do? We chose to find a solution at the
higher level.
So we split then into then, done and map.
then is fed the previous promise value and requires you return a promise.
done is fed the previous promise value and returns a Void promise (which is 80% of chain usage)
map is fed the previous promise value and requires you return a non-promise, ie. a value.
Hence .then { (user) -> Void in is no longer valid and that's why you're getting an error.
It's now designed to return a Thenable like so:
userPromise.then { user -> Promise<User> in
//return yet another promise
}
The .then that used to return Void is now it's own .done.
i.e:
userPromise.done { (user) in
print(user)
}
So when you mix 'em up:
We get:
userPromise
.then { (user) -> Promise<User> in
//return another Promise
return self.uppercasePromise(on: user)
}
.done { (user) in
/*
Depending on your sequence, no more promises are left
and you should have a matured user object by now
*/
print(user)
}
.catch { (error) in
print(error)
}
.finally {
print("finally")
}
func uppercasePromise(on user: User) -> Promise<User> {
/*
Didn't understand your statement but do whatever you meant when you said:
"Once I get the user details I want to make a full name, "
uppercase promises which are dependent on userPromise."
*/
return Promise { seal in
seal.fulfill(user)
}
}

Return a completable in RxSwift without using a create block

I have a Completable being returned from a simple function.
This is not an async call, so I just need to return a succcessful completion or error depending on a conditional (using Rx here so I can tie into other Rx usages):
func exampleFunc() -> Completable {
if successful {
return Completable.just() // What to do here???
} else {
return Completable.error(SomeErrorType.someError)
}
}
The error case works pretty easily, but am having a block on how to just return a successful completable (without needing to .create() it).
I was thinking I just need to use Completable's .just() or .never(), but just is requiring a parameter, and never doesn't seem to trigger the completion event.
.empty() is the operator I was looking for!
Turns out, I had mixed up the implementations of .never() and .empty() in my head!
.never() emits no items and does NOT terminate
.empty() emits no items but does terminates normally
So, the example code above works like this:
func exampleFunc() -> Completable {
if successful {
return Completable.empty()
} else {
return Completable.error(SomeErrorType.someError)
}
}
Here is the documentation on empty/throw/never operators.
I would be more inclined to do the following:
func example() throws {
// do something
if !successful {
throw SomeErrorType.someError
}
}
Then in order to tie it into other Rx code, I would just use map as in:
myObservable.map { try example() }
But then, mapping over a Completable doesn't work because map's closure only gets called on next events. :-(
I tend to avoid Completable for this very reason, it doesn't seem to play well with other observables. I prefer to use Observable<Void> and send an empty event before the completed...
Something like this:
let chain = Observable<Void>.just()
let foo = chain.map { try example() }
foo.subscribe { event in print(event) }

RxSwift: Connect to Connectable Observable multipe times

I am adopting the MVVM pattern in my iOS application. I expose a range of Observables as public properties in my view model and bind the UI to these properties. These Observables are created from a private connectable observable.
A view controller class then calls the "execute" method to fire the network request. However, if it fails for any reason, I'd like to be able to call "execute" again but this does not work. I believe this is due to the fact that the connectable observable has completed.
How can I achieve this without having to recreate the view model each time? I know I could do this by transforming a simple execute publish subject to the userDetailsObservable by using flatMap but I rely on the onCompleted event for other functionality. The onCompleted event would be lost as the publish subject remains active.
Connectable Observable Solution
class ViewModel {
public var userName: Observable<String> {
self.userDetailsObservable.map {
return $0["username"]
}
}
public var address: Observable<String> {
self.userDetailsObservable.map {
return $0["address"]
}
}
public func execute() {
self.userDetailsObservable.connect()
}
private lazy var userDetailsObservable: ConnectableObservable<JSON> {
return Observable.create { observer in
// execute network request
// assume there is a json object and error object returned
if error != nil {
observer.onError(error)
} else {
observer.onNext(json)
}
observer.onCompleted()
}.publish()
}
}
The FlatMap solution
This would execute the network request every time an event is pushed on the execute subject. (execute.onNext()). The problem here is that the onCompleted event is lost as we are transforming a publish subject.
class ViewModel {
public var userName: Observable<String> {
self.userDetailsObservable.map {
return $0["username"]
}
}
public var address: Observable<String> {
self.userDetailsObservable.map {
return $0["address"]
}
}
public var execute: PublishSubject<Void>()
private lazy var userDetailsObservable: Observable<JSON> {
return self.execute.flatMapLatest { _ in
Observable.create { observer in
// execute network request
// assume there is a json object and error object returned
if error != nil {
observer.onError(error)
} else {
observer.onNext(json)
}
observer.onCompleted()
}
}.share()
}
You should use catchError and return a default value ("" for instance).
It’s required to prevent the observable from being disposed when you receive an error from the API.

Resources