adding the duplicate data to the array: error - swift

my problem is adding the duplicate data to the array
my program it works well before it can be refresh manually but duplicate added to list when manually refreshed
when I check the print, the data is added to the double list
print result
ARRAYLAR : ["EXAMPLE", "EXAMPLE"]
Watch the video for a better understanding of the problem
VİDEO
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
getData()
}
#objc func getData() {
self.konuAdiArray.removeAll(keepingCapacity: false)
self.konuHedefTarihArray.removeAll(keepingCapacity: false)
self.konuTestArray.removeAll(keepingCapacity: false)
self.konuIDArray.removeAll(keepingCapacity: false)
self.veriGirisArray.removeAll(keepingCapacity: false)
Database.database().reference().child("users").child((Auth.auth().currentUser?.uid)!).child("dersler").child(gelenDersID!).child("konular").observe(DataEventType.childAdded) { (snapshot) in
let values = snapshot.value! as! NSDictionary
self.konuAdiArray.append(values["konuAdi"]as! String)
self.konuHedefTarihArray.append(values["konuHedefTarihi"]as! String)
self.konuTestArray.append(values["konuTestHedefi"]as! String)
self.veriGirisArray.append(values["veriGirisSoru"]as! String)
self.konuIDArray.append(snapshot.key)
print("ARRAYLAR : \(self.konuAdiArray)")
self.tableView.reloadData()
}
}
refreshBarButton code
#IBAction func refreshBarButton(_ sender: Any) {
getData()
}

You need to make separate function for observing changes of your data base. When you call getData() you code calls twice of DB changes. So that you subscribe on changes one more time that's why I'd recommend you to make separate function like setDataBaseObserver() which you call only one time in viewDidLoad. If you are updating your data you should make network request or take them from different source (not from data base again). Hope you understand me right!
func setDataBaseObserver() {
Database.database().reference().child("users").child((Auth.auth().currentUser?.uid)!).child("dersler").child(gelenDersID!).child("konular").observe(DataEventType.childAdded) { (snapshot) in
let values = snapshot.value! as! NSDictionary
self.konuAdiArray.append(values["konuAdi"]as! String)
self.konuHedefTarihArray.append(values["konuHedefTarihi"]as! String)
self.konuTestArray.append(values["konuTestHedefi"]as! String)
self.veriGirisArray.append(values["veriGirisSoru"]as! String)
self.konuIDArray.append(snapshot.key)
print("ARRAYLAR : \(self.konuAdiArray)")
self.tableView.reloadData()
}
}
Hope it will help to you!

There's no reason for the manual refresh. You are observing childAdded on a Firebase database, which will continue to update in real time. Whenever you hit the manual refresh, your getData() is adding your controller as an observer again. Either remove the manual refresh control and just let Firebase do its thing (this is what it excels at - realtime updates without manual refresh), or change your childAdded observation to be a one-time data fetch.

Related

Driver is being triggered constantly between View Model and View Controller

Have an issue with a Driver on RxSwift. Have a view model who is listening to an initTrigger in a ViewController as follow.
let initTrigger = rx.viewWillAppear
.mapToVoid()
.asDriverOnErrorJustComplete()
This initTrigger is used to bind to another Driver on the view model
let shoppingCart: Driver<ShoppingCart>
let shoppingCart = input.initTrigger
.flatMapLatest {
self.getShoppingCartUseCase
.execute()
.asDriver(onErrorJustReturn: ShoppingCart())
}
getShoppingCartUseCase.execute() returns Observable<ShoppingCart> and is using RxRealm lo listen to changes to a database.
back on the view controller, I have subscribed to that shoppingCart like this
output?.shoppingCart
.map {
print("Mapping")
return $0.lines.count == 0
}
.asObservable()
.bind(to: goToCartButton.rx.isHidden)
.disposed(by: bag)
I placed the print("Mapping") to realize that this last Driver is being triggered constantly after making an action that modifies my model and triggers the Observable<ShoppingCart> I mentioned before.
What I'm doing wrong here?
Thanks for your help.
First of all you can use .distincUntilChanged() to filter identical events.
second of all, check why .getShoppingCartUseCase keeps on emitting events, RxRealm will send updates whenever ShoppingCart is written to the db, so maybe you have some unnesessary writes. make sure when you write to realm you use .modified flag, not .all (which will override an item only if it has changed, and won't cause event if it hasn't)
If you sure you only need to an event once - you can always add .take(1)
Also you call it initTrigger, but send it on viewWillAppear - which can be called as many times as you getting back to the screen. If you need it once, put it on viewDidLoad
PS instead of .asObservable().bind(to:...) you can just write .drive(...) which is cleaner way to bind drivers to ui.
To stop subscription observer have to do one of the following:
Send error message
Send completed message
Dispose subscription (destroy disposeBag)
In your case nor rx.viewWillAppear neither shoppingCart not sending error or completed messages, cause they are Drivers
One way for you to stop subscription correctly is to destroy old disposeBag
bag = DisposeBag()
But don't forget to restore subscription on viewWillAppear
Other option would be to have some flag in VC like
var hasAppeared: Bool
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
...
hasAppeared = true
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisppear(animated)
...
hasAppeared = false
}
and just to add filtering
output?.shoppingCart
.filter({ [weak self] _ in self?.hasAppeared ?? false })
.map {
print("Mapping")
return $0.lines.count == 0
}
.asObservable()
.bind(to: goToCartButton.rx.isHidden)
.disposed(by: bag)
The third way is to stop sending from inside viewModel
let initTrigger = rx.viewWillAppear
.mapToVoid()
.asDriverOnErrorJustComplete()
let stopTrigger = rx.viewWillDisappear
.mapToVoid()
.asDriverOnErrorJustComplete()
let shoppingCart: Driver<ShoppingCart>
let shoppingCart = Observable.merge(input.initTrigger.map({ true }),
input.stopTrigger.map({ false }))
.flatMapLatest { isRunning in
guard isRunning else {
return .just(ShoppingCart())
}
return self.getShoppingCartUseCase
.execute()
.asDriver(onErrorJustReturn: ShoppingCart())
}

Way to check data modification across many viewcontroller without using global variables?

I have an app which contains several viewControllers. On the viewDidAppear() of the first VC I call a set of functions which populate some arrays with information pulled from a database and then reload table data for a tableView. The functions all work perfectly fine and the desired result is achieved every time. What I am concerned about is how often viewDidAppear() is called. I do not think (unless I am wrong) it is a good idea for the refreshing functions to be automatically called and reload all of the data every time the view appears. I cannot put it into the viewDidLoad() because the tableView is part of a tab bar and if there are some modifications done to the data in any of the other tabs, the viewDidLoad() will not be called when tabbing back over and it would need to reload at this point (as modifications were made). I thought to use a set of variables to check if any modifications were done to the data from any of the other viewControllers to then conditionally tell the VDA to run or not. Generally:
override func viewDidAppear(_ animated: Bool) {
if condition {
//run functions
} else{
//don't run functions
}
}
The issue with this is that the data can be modified from many different viewControllers which may not segue back to the one of interest for the viewDidAppear() (so using a prepareForSegue wouldn't work necessarily). What is the best way to 'check' if the data has been modified. Again, I figured a set of bool variables would work well, but I want to stay away from using too many global variables. Any ideas?
Notification Center
struct NotificationName {
static let MyNotificationName = "kMyNotificationName"
}
class First {
init() {
NotificationCenter.default.addObserver(self, selector: #selector(self.notificationReceived), name: NotificationName.MyNotificationName, object: nil)
}
func notificationReceived() {
// Refresh table view here
}
}
class Second {
func postNotification() {
NotificationCenter.default.post(name: NotificationName.MyNotificationName, object: nil)
}
}
Once postNotification is called, the function notificationReceived in class First will be called.
Create a common global data store and let all the view controllers get their data from there. This is essentially a global singleton with some accompanying functions. I know you wanted to do this without global variables but I think you should consider this.
Create a class to contain the data. Also let it be able to reload the data.
class MyData {
static let shared = MyData()
var data : SomeDataType
func loadData() {
// Load the data
}
}
Register to receive the notification as follows:
static let dataChangedNotification = Notification.Name("DataChanged")
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
}
override func viewDidLoad() {
super.viewDidLoad()
// Establish a way for call activity to notify this class so it can update accordingly
NotificationCenter.default.addObserver(self, selector: #selector(handleDataChangedNotification(notification:)), name: "DataChanged", object: nil)
}
func handleDataChangedNotification(notification: NSNotification) {
// This ViewController was notified that data was changed
// Do something
}
func getDataToDisplay() {
let currentData = MyData.shared.data
// do something
}
// Any view controller would call this function if it changes the data
func sendDataChangeNotification() {
let obj = [String]() // make some obj to send. Pass whatever custom data you need to send
NotificationCenter.default.post(name: type(of: self).dataChangedNotification, object: obj)
}

With Firebase, Swift removeObserver(withHandle does not remove the observer

With removeObserver(withHandle in Swift 3, the Observer is not removed on viewDidDisappear
var query = FIRDatabaseQuery()
var postRef: FIRDatabaseReference!
var postRefHandle: FIRDatabaseHandle?
override func viewDidLoad() {
super.viewDidLoad()
postRef = baseRef.child("Posts")
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
if postRefHandle != nil {
//try 1:
//postRef.removeObserver(withHandle: postRefHandle!)
//try 2:
//postRef.queryOrdered(byChild: "sortTimestamp").removeObserver(withHandle: postRefHandle!)
//try 3:
//query.removeObserver(withHandle: postRefHandle!)
}
//try 4:
//postRef.removeAllObservers() //works
}
func getPosts()
{
var count = 20
query = postRef.queryOrdered(byChild: "sortTimestamp")
postRefHandle = query.queryLimited(toFirst: UInt(count)).observe(.childAdded //etc.
}
So I tried the three methods in viewDidDisappear, but the observer is not removed.
try 3 query.removeObserver(withHandle: postRefHandle!) as by answer from Firebase, how do I return a handle so that I can call removeObserver? by frank-van-puffelen
The only one that does work is the one outlined in try 4.
Any reason why I cannot remove the Observer with removeObserver(withHandle? (try 1 - 3)
Also "query.queryLimited(toFirst: UInt(count)).observe(.childAdded" does not get the latest data from Firebase. I was under the impression the observe always gets the updated data, as opposed to observeSingleEvent. Why does it not do that?
Any suggestions are much appreciated.
If you have the following code:
var postsRef: FIRDatabaseReference!
var postRefHandle: FIRDatabaseHandle!
var query = FIRDatabaseQuery()
func addHandler() {
self.postsRef = self.ref.child("posts")
var count = 20
self.query = self.postsRef.queryOrdered(byChild: "sortTimestamp")
self.postRefHandle = self.query.queryLimited(toFirst: UInt(count)).observe(.childAdded, with: { snapshot in
print(snapshot)
})
}
and at a later time you do this function
self.postsRef.removeObserver(withHandle: self.postRefHandle!)
It removes the observer. This is tested code.
To the second part of your question: querySingleEvent and observe do the same thing data wise but have different behaviors. They will both always get current data - modified by startAt, endAt, equalTo etc.
observeSingleEvent returns the data, does NOT leave an observer so you
will not be notified if that data changes
observe returns the data and leaves an observer attached to the node
and will notify you of future changes.
.childAdded: when any children are added to the node
.childChanges: when any children change in the node
.childRemoved: when a child is removed.
How I'm Able to Achieve this is by removing child reference.
var recentRef: FIRDatabaseReference!
recentRef.child("\(groupId)").observe(.value, with: { (snapshot) in
recentRef.removeAllObservers() // not_working
recentRef.child("\(groupId)").removeAllObservers() //working
if let obj = snapshot.value as? [String: AnyObject] {
//... code here
}
})
You can achieve this without making a query also(Swift 4) -
This removes the reference of the observer properly and works for me.
private let ref = Database.database().reference().child("classTalks")
private var refHandle: DatabaseHandle!
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear()
refHandle = ref.observe(.value, with: { (snapshot) in
...
})
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear()
ref.removeObserver(withHandle: refHandle)
}

Realm, best approach to save\update\Observe String to Realm Object

(looking for the best approach to save Realm property.
I have a UIViewController with a lot of TextView, etc that I fill from a Realm object.
Each time the textfield are modified, I need to send back to change un the realm property.
The (not cool) thing, are that I cannot save directly, I have to open a write transaction.
object.propertyA= “hello” // crash
try! realm.write { //work
userBeer?.Name = lblbeerName.text!
}
So, i found a bit painfull (and not clean) to to that for all text.
I’ve looked at rxRealm, but cannot see any (newbies) sample to make that.
So, I have 2 approach un mind
Modify the model getters and setters for the property
var beerName: String? {
get {
return self.Name
}
set {
try! realm.write {
self.txtName=beerName!
}
}
use the RXSwift approach from here (https://www.raywenderlich.com/149753/bond-tutorial-bindings-swift)
Bing the TextField.text to a var String, and observe this string to write.
What do you think?
My perfect world will be to find a way to bing the TextField.text property directly, something like:
myRealmObject.property.BindTo(self.txtName)
Katsumi from Realm here. Although it is not the best approach, I propose another way. You can use realm.beginWrite() and try! realm.commitWrite() instead of block-based API for a long-lived transaction.
For example, you can open a transaction when the view appeared, and then close the transaction when the view disappeared, like the following:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
realm.beginWrite()
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
try! realm.commitWrite()
}
In this way, you can assign a value to the Realm object at any time in the view.
Be careful not to leave the transaction open. To avoid increasing file size, do not update data frequently in the background while transactions are open.
Change the property within the write block:
try! realm.write {
object.propertyA = “hello”
userBeer?.Name = lblbeerName.text!
}

How to reload UITableView without printing data twice?

I have a single view of SlackTextViewController which works as a UITableView. I'm switching between "states" using a public String allowing it to read 1 set of data in a certain state and another set of data in another state. The problem is when I switch back and forth to the original state it prints the data 2, 3, 4 times; as many as I go back and forth. I'm loading the data from a Firebase server and I think it may be a Firebase issue. Here is my code...
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
if chatState == "ALL"
{
self.globalChat()
}else if chatState == "LOCAL"
{
self.localChat()
}
}
override func viewDidDisappear(animated: Bool) {
self.messageModels.removeAll()
tableView.reloadData()
ref.removeAllObservers()
}
func chatACTN()
{
if chatState == "ALL"
{
self.viewWillAppear(true)
}else if chatState == "LOCAL"
{
self.viewWillAppear(true)
}
}
func globalChat()
{
self.messageModels.removeAll()
tableView.reloadData()
let globalRef = ref.child("messages")
globalRef.keepSynced(true)
globalRef.queryLimitedToLast(100).observeEventType(.ChildAdded, withBlock: { (snapshot) -> Void in
if snapshot.exists()
{
let names = snapshot.value!["name"] as! String
let bodies = snapshot.value!["body"] as! String
let avatars = snapshot.value!["photo"] as! String
let time = snapshot.value!["time"] as! Int
let messageModel = MessageModel(name: names, body: bodies, avatar: avatars, date: time)
self.messageModels.append(messageModel)
self.messageModels.sortInPlace{ $0.date > $1.date }
}
self.tableView.reloadData()
})
}
func localChat()
{
self.messageModels.removeAll()
tableView.reloadData()
print("LOCAL")
}
The problem is that each time you call globalChat() you're creating another new observer which results in having multiple observers adding the same items to self.messageModels. Thats why you're seeing the data as many times as you switch to the global state.
Since you want to clear the chat and load the last 100 each time you switch to global, there's no point in keeping the observer active when you switch to "Local".
Just remove the observer when you switch to Local, that should fix your problem.
From firebase docs:
- (void) removeAllObservers
Removes all observers at the current reference, but does not remove any observers at child references.
removeAllObservers must be called again for each child reference where a listener was established to remove the observers.
So, ref.removeAllObservers() will not remove observers at ref.child("messages") level.
Using ref.child("messages").removeAllObservers at the beginning of localChat function to remove the observer you created in globalChat would be ok if you're only dealing with this one observer at this level but if you have more on the same level or you think you might add more in the future the best and safest way would be to remove the specific observer you created. To do that you should use the handle that is returned from starting an observer. Modify your code like this:
var globalChatHandle : FIRDatabaseHandle?
func globalChat()
{
self.messageModels.removeAll()
tableView.reloadData()
ref.child("messages").keepSynced(true)
globalChatHandle = ref.child("messages").queryLimitedToLast(100).observeEventType(.ChildAdded, withBlock: { (snapshot) -> Void in
if snapshot.exists()
{
let names = snapshot.value!["name"] as! String
let bodies = snapshot.value!["body"] as! String
let avatars = snapshot.value!["photo"] as! String
let time = snapshot.value!["time"] as! Int
let messageModel = MessageModel(name: names, body: bodies, avatar: avatars, date: time)
self.messageModels.append(messageModel)
self.messageModels.sortInPlace{ $0.date > $1.date }
}
self.tableView.reloadData()
})
}
func localChat()
{
if globalChatHandle != nil {
ref.child("messages").removeObserverWithHandle(globalChatHandle)
}
self.messageModels.removeAll()
tableView.reloadData()
print("LOCAL")
}
And in viewDidDisappear method replace this
ref.removeAllObservers()
with
ref.child("messages").removeAllObservers()