LiveData Observer Called on initialization & ObserveOnce not working - android-livedata

I'm trying to use android live data to observe completion status of an async task in my viewmodel from my fragment. So i considered using ObserveOnce from this post LiveData remove Observer after first callback
// in ViewModel
var status= MutableLiveData<Boolean>()
fun asyncTask(){
// do some async task
asyncTask.addOnSuccessListener{
status.value = true
}
asyncTask.addOnFaiureListener{
status.value = false
}
}
// In Fragment
fun startProcess(){
viewmodel.status.value = false
viewmodel.asyncTask
viewmodel.status.observeOnce(viewLifecycleOwner, Observer { it ->
if(it){
Toast.maketext(requireActivity(),"Task Done",Toast.LENGTH_SHORT).maketext()
}else{
Toast.maketext(requireActivity(),"Task Failed",Toast.LENGTH_SHORT).maketext()
}
})
}
The problem here is that this observeOnce is called immediately after initilization, and is always showing false.
I dont understand what is wrong here!!

You actually set a value for your status which will be emitted as soon as the observer will have an active state.
If LiveData already has data set, it will be delivered to the observer.
...
When data changes while the owner is not active, it will not receive any updates. If it becomes active again, it will receive the last available data automatically.
Do not set the initial value unless you need a default to start with.

Related

How to receive continuous stream of states from two publishers working together

I have a function getState(), the purpose of which is to return a continuous stream of States. I have two publishers: statePublisher and requestPipeline.
When I call getState(), the requestPipeline is sent a Request. As the pipeline progresses, it returns states to the statePublisher.
This statePublisher gets referenced strongly during the operator .doThingThree(statePublisher). This operator presents a View Controller which holds on to the statePublisher. When that view controller dismisses, it sends a State and then completes.
This is my current, broken, code:
func getState(request: Request) -> AnyPublisher<State, Never> {
let statePublisher = PassthroughSubject<State, Never>()
let requestPipeline = PassthroughSubject<Request, Error>()
requestPublisher = requestPipeline
.eraseToAnyPublisher()
.doThingOne()
.doThingTwo(statePublisher)
.doThingThree(statePublisher)
requestPipeline.send(request)
return statePublisher
.eraseToAnyPublisher()
}
func getMyState() {
cancellable = getState(request: myRequest)
.sink { state in
print(state) // Never gets fired
}
}
Unfortunately calling getMyState() doesn't do anything. Do I need to subscribe to the requestPublisher before I can do anything? Any help appreciated!
You do have to subscribe to a PassthroughSubject before you send any values through it in order to receive events.
In your code you call getState.
getState calls send.
The send is ignored because there are no subscribers to requestPipeline.
Then getState returns.
You then you subscribe to statePublisher with sink.
Your code doesn't show any values ever being sent to statePublisher.
If there are were events being sent in doThingTwo and doThingThree then they are sent long before sink subscribes to statePublisher.
But then again doThingTwo and doThingThree are never invoked in the code that you've shown because there are no subscribers to requestPublisher or requestPipeline
If you are unsure if your publishers are executing use the .print(<name>) operator to see subscriptions and values passing through the pipeline.

iOS - RxRealm - Is it possible to get updated event even if dataset is not changed

iOS - RxRealm - Is it possible to get updated event even if the dataset is not changed
Is it possible in RxRealm to get a notification even if my dataset is the same?
When I subscribe to the collection (no items at app launch) I get back an empty array. (it's fine at this point)
My problem is that my API request may return with an empty array as well, and in this case, I didn’t get any notification from the RxRealm.collection, So I don't know if my request is finished or I was just reading the database.
I tried to call realm.refresh() but it didn't help
This is what I'm observing:
func findAll() -> Results<T> {
return realm.objects(T.self)
}
.
.
.
Observable.collection(from: findAll()).map(Array.init)
This is how I rebuild my database after API request is finished:
func rebuildDatabase(objects: [T], with update: Bool = false) {
realm.refresh() // trying to force refresh event
do {
realm.beginWrite()
realm.delete(self.findAll())
realm.add(objects, update: update)
try realm.commitWrite()
} catch {
Log.error(error.localizedDescription)
}
}
Is it possible to archive this behavior with some configuration or is there any line of code that will force the notification? Thank for all the answers

CFNotificationCenter repeating events statements

I have been working on an enterprise iOS/Swift (iOS 11.3, Xcode 9.3.1) app in which I want to be notified if the screen changes (goes blank or becomes active) and capture the events in a Realm databse. I am using the answer from tbaranes in detect screen unlock events in IOS Swift and it works, but I find added repeats as the screen goes blank and becomes active:
Initial Blank: a single event recorded
Initial Re-activiation: two events are recorded
Second Blank: two events are recorded
Second Re-act: three events are recorded
and this cycle of adding an additional event recording each cycle.
This must be something in the code (or missing from the code) that is causing an additive effect but I can’t find it. And, yes, the print statements show the issue is not within the Realm database, but are actual repeated statements.
My code is below. Any suggestions are appreciated.
CFNotificationCenterAddObserver(CFNotificationCenterGetDarwinNotifyCenter(), //center
Unmanaged.passUnretained(self).toOpaque(), // observer
displayStatusChangedCallback, // callback
"com.apple.springboard.hasBlankedScreen" as CFString, // event name
nil, // object
.deliverImmediately)
}
private let displayStatusChangedCallback: CFNotificationCallback = { _, cfObserver, cfName, _, _ in
guard let lockState = cfName?.rawValue as String? else {
return
}
let catcher = Unmanaged<AppDelegate>.fromOpaque(UnsafeRawPointer(OpaquePointer(cfObserver)!)).takeUnretainedValue()
catcher.displayStatusChanged(lockState)
print("how many times?")
}
private func displayStatusChanged(_ lockState: String) {
// the "com.apple.springboard.lockcomplete" notification will always come after the "com.apple.springboard.lockstate" notification
print("Darwin notification NAME = \(lockState)")
if lockState == "com.apple.springboard.hasBlankedScreen" {
print("A single Blank Screen")
let statusString = dbSource() // Realm database
statusString.infoString = "blanked screen"
print("statusString: \(statusString)")
statusString.save()
return
}

Is it possible to turn off drawing the UI during the next run loop cycle?

Is there any way I can temporarily disable the drawing of the UI (and the progression of animations and other related things)?
I have a notification system which coalesces notifications, so that multiple notifications which happen in a single run loop cycle are coalesced into one for the interested party, kind of like how setNeedsDisplay can be called any number of times in a single cycle, but the drawing only happens once.
The problem is, the way I'm coalescing notifications is to schedule the forward notification on the next run loop, and skip new updates in the meantime, kind of like this:
private var startedCoalescingUpdates = false
func receiveNotification() {
guard startedCoalescingUpdates == false else {
return
}
startedCoalescingUpdates = true
OperationQueue.main.addOperation {
self.setForwardNotification = false
self.delegate.updateOccurred()
}
}
Notifications can be chained together, so that one notification triggers another notification, which can trigger another, etc.
Because OperationQueue.main.addOperation happens on the next cycle (or at least it does if I call it from another block which was scheduled in addOperation), some UI drawing occurs whilst the notifications propagate. The UI can show several frames which look incorrect; I'd rather not draw the UI or respond to events, etc, until the notifications are all done, in order for all the data to be fully ready before its drawn for the first time. I'd probably do something like:
private var startedCoalescingUpdates = false
func receiveNotification() {
guard startedCoalescingUpdates == false else {
return
}
startedCoalescingUpdates = true
//////
stopDrawingUI()
//////
OperationQueue.main.addOperation {
self.setForwardNotification = false
//////
resumeDrawingUI()
//////
self.delegate.updateOccurred()
}
}
Is this possible?

Alamofire request blocking during application lifecycle

I'm having troubles with Alamofire using Operation and OperationQueue.
I have an OperationQueue named NetworkingQueue and I push some operation (wrapping AlamofireRequest) into it, everything works fine, but during application living, at one moment all Alamofire request are not sent. My queue is getting bigger and bigger and no request go to the end.
I do not have a scheme to reproduce it anytime.
Does anybody have a clue for helping me?
Here is a sample of code
The BackgroundAlamoSession
let configuration = URLSessionConfiguration.background(withIdentifier: "[...].background")
self.networkingSessionManager = Alamofire.SessionManager(configuration: configuration)
AbstractOperation.swift
import UIKit
import XCGLogger
class AbstractOperation:Operation {
private let _LOGGER:XCGLogger = XCGLogger.default
enum State:String {
case Ready = "ready"
case Executing = "executing"
case Finished = "finished"
var keyPath: String {
get{
return "is" + self.rawValue.capitalized
}
}
}
override var isAsynchronous:Bool {
get{
return true
}
}
var state = State.Ready {
willSet {
willChangeValue(forKey: self.state.rawValue)
willChangeValue(forKey: self.state.keyPath)
willChangeValue(forKey: newValue.rawValue)
willChangeValue(forKey: newValue.keyPath)
}
didSet {
didChangeValue(forKey: oldValue.rawValue)
didChangeValue(forKey: oldValue.keyPath)
didChangeValue(forKey: self.state.rawValue)
didChangeValue(forKey: self.state.keyPath)
}
}
override var isExecuting: Bool {
return state == .Executing
}
override var isFinished:Bool {
return state == .Finished
}
}
A concrete Operation implementation
import UIKit
import XCGLogger
import SwiftyJSON
class FetchObject: AbstractOperation {
public let _LOGGER:XCGLogger = XCGLogger.default
private let _objectId:Int
private let _force:Bool
public var object:ObjectModel?
init(_ objectId:Int, force:Bool) {
self._objectId = objectId
self._force = force
}
convenience init(_ objectId:Int) {
self.init(objectId, force:false)
}
override var desc:String {
get{
return "FetchObject(\(self._objectId))"
}
}
public override func start(){
self.state = .Executing
_LOGGER.verbose("Fetch object operation start")
if !self._force {
let objectInCache:objectModel? = Application.main.collections.availableObjectModels[self._objectId]
if let objectInCache = objectInCache {
_LOGGER.verbose("object with id \(self._objectId) founded on cache")
self.object = objectInCache
self._LOGGER.verbose("Fetch object operation end : success")
self.state = .Finished
return
}
}
if !self.isCancelled {
let url = "[...]\(self._objectId)"
_LOGGER.verbose("Requesting object with id \(self._objectId) on server")
Application.main.networkingSessionManager.request(url, method : .get)
.validate()
.responseJSON(
completionHandler: { response in
switch response.result {
case .success:
guard let raw:Any = response.result.value else {
self._LOGGER.error("Error while fetching json programm : Empty response")
self._LOGGER.verbose("Fetch object operation end : error")
self.state = .Finished
return
}
let data:JSON = JSON(raw)
self._LOGGER.verbose("Received object from server \(data["bId"])")
self.object = ObjectModel(objectId:data["oId"].intValue,data:data)
Application.main.collections.availableobjectModels[self.object!.objectId] = self.object
self._LOGGER.verbose("Fetch object operation end : success")
self.state = .Finished
break
case .failure(let error):
self._LOGGER.error("Error while fetching json program \(error)")
self._LOGGER.verbose("Fetch object operation end : error")
self.state = .Finished
break
}
})
} else {
self._LOGGER.verbose("Fetch object operation end : cancel")
self.state = .Finished
}
}
}
The NetworkQueue
class MyQueue {
public static let networkQueue:SaootiQueue = SaootiQueue(name:"NetworkQueue", concurent:true)
}
How I use it in another operation and wait for for result
let getObjectOperation:FetchObject = FetchObject(30)
SaootiQueue.networkQueue.addOperations([getObjectOperation], waitUntilFinished: true)
How I use it the main operation using KVO
let getObjectOperation:FetchObject = FetchObject(30)
operation.addObserver(self, forKeyPath: #keyPath(Operation.isFinished), options: [.new], context: nil)
operation.addObserver(self, forKeyPath: #keyPath(Operation.isCancelled), options: [.new], context: nil)
queue.addOperation(operation)
//[...]
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if let operation = object as? FetchObject {
operation.removeObserver(self, forKeyPath: #keyPath(Operation.isFinished))
operation.removeObserver(self, forKeyPath: #keyPath(Operation.isCancelled))
if keyPath == #keyPath(Operation.isFinished) {
//Do something
}
}
A few clarifications:
My application is a radio player and I need, while playing music and the background, to fetch the currently playing program. This is why I need background Session.
In fact I also use the background session for all the networking I do when the app is foreground. Should I avoid that ?
The wait I'm using is from another queue and is never used in the main queue (I know it is a threading antipattern and I take care of it).
In fact it is used when I do two networking operation and the second one depends of the result of the second. I put a wait after the first operation to avoid KVO observing. Should I avoid that ?
Additional edit:
When I say "My queue is getting bigger and bigger and no request go to the end", it means that at one moment during application livecycle, random for the moment (I can not find a way to reproduce it at every time), Alamofire request don't reach the response method.
Because of that the Operation wrapper don't end and the queue is growing.
By the way I'm working on converting Alamofire request into URLRequest for having clues and I founded some problem on using the main queue. I have to sort what is due to the fact that Alamofire use the main queue for reponse method and I'll see if I find a potential deadlock
I'll keep you informed. Thanks
There are minor issues, but this operation implementation looks largely correct. Sure, you should make your state management thread-safe, and there are other stylistic improvements you could make, but I don't think this is critical to your question.
What looks worrisome is addOperations(_:waitUntilFinished:). From which queue are you waiting? If you do that from the main queue, you will deadlock (i.e. it will look like the Alamofire requests never finish). Alamofire uses the main queue for its completion handlers (unless you override the queue parameter of responseJSON), but if you're waiting on the main thread, this can never take place. (As an aside, if you can refactor so you never explicitly "wait" for operations, that not only avoids the deadlock risk, but is a better pattern in general.)
I also notice that you're using Alamofire requests wrapped in operations in conjunction with a background session. Background sessions are antithetical to operations and completion handler closure patterns. Background sessions continue after your app has been jettisoned and you have to rely solely upon the SessionDelegate closures that you set when you first configure your SessionManager when the app starts. When the app restarts, your operations and completion handler closures are long gone.
Bottom line, do you really need background session (i.e. uploads and downloads that continue after your app terminates)? If so, you may want to lose this completion handler and operation based approach. If you don't need this to continue after the app terminates, don't use background sessions. Configuring Alamofire to properly handle background sessions is a non-trivial exercise, so only do so if you absolutely need to. Remember to not conflate background sessions and the simple asynchronous processing that Alamofire (and URLSession) do automatically for you.
You asked:
My application is a radio player and I need, while playing music and the background, to fetch the currently playing program. This is why I need background Session.
You need background sessions if you want downloads to proceed while the app is not running. If your app is running in the background, though, playing music, you probably don't need background sessions. But, if the user chooses to download a particular media asset, you may well want background session so that the download proceeds when the user leaves the app, whether the app is playing music or not.
In fact I also use the background session for all the networking I do when the app is foreground. Should I avoid that ?
It's fine. It's a little slower, IIRC, but it's fine.
The problem isn't that you're using background session, but that you're doing it wrong. The operation-based wrapping of Alamofire doesn't make sense with a background session. For sessions to proceed in the background, you are constrained as to how you use URLSession, namely:
You cannot use data tasks while the app is not running; only upload and download tasks.
You cannot rely upon completion handler closures (because the entire purpose of background sessions is to keep them running when your app terminates and then fire up your app again when they're done; but if the app was terminated, your closures are all gone).
You have to use delegate based API only for background sessions, not completion handlers.
You have to implement the app delegate method to capture the system provided completion handler that you call when you're done processing background session delegate calls. You have to call that when your URLSession tells you that it's done processing all the background delegate methods.
All of this is a significant burden, IMHO. Given that the system is keeping you app alive for background music, you might contemplate using a standard URLSessionConfiguration. If you're going to use background session, you might need to refactor all of this completion handler-based code.
The wait I'm using is from another queue and is never used in the main queue (I know it is a threading antipattern and I take care of it).
Good. There's still serious code smell from ever using "wait", but if you are 100% confident that it's not deadlocking here, you can get away with it. But it's something you really should check (e.g. put some logging statement after the "wait" and make sure you're getting past that line, if you haven't already confirmed this).
In fact it is used when I do two networking operation and the second one depends of the result of the second. I put a wait after the first operation to avoid KVO observing. Should I avoid that ?
Personally, I'd lose that KVO observing and just establish addDependency between the operations. Also, if you get rid of that KVO observing, you can get rid of your double KVO notification process. But I don't think this KVO stuff is the root of the problem, so maybe you defer that.

Resources