I am trying to retrieve the data set in an array in one function, in a different function. I appended the data i want into the OtherUserArray in the
createNewConversation function. However when i try the retrieve this data in the sendMessage function, it prints an empty array.
import MessageKit
import InputBarAccessoryView
import FirebaseAuth
import Firebase
import Foundation
final class DatabaseManager {
static let shared = DatabaseManager()
var foundOtherUser:String?
var OtherUserArray = [String]()
}
extension DatabaseManager {
// create a new conversation with target user, uid and first sent message
public func createNewConversation(with otherUserUid: String, name: String, firstMessage: Message, completion: #escaping(Bool) -> Void) {
let db = Firestore.firestore()
let CurrentUser = Auth.auth().currentUser?.uid
let defaults = UserDefaults.standard
defaults.set(CurrentUser, forKey: "uid")
guard let currentUid = UserDefaults.standard.string(forKey: "uid"), let currentName = UserDefaults.standard.string(forKey: "usersFullname") else {
return
}
let messageDate = firstMessage.sentDate
let dateString = MessageViewController.dateFormatter.string(from: messageDate)
var message = ""
let conversationId = firstMessage.messageId
let newConversationData: [String:Any] = [
"id": conversationId,
"other_user-uid": otherUserUid,
"name": name,
"latest-message": [
"date": dateString,
"message": message,
"is-read": false
]
]
// save other user uid to global var
OtherUserArray.append(otherUserUid)
}
}
}
public func sendMessage(to conversation: String, name: String, newMessage: Message, completion: #escaping(Bool) -> Void) {
// update the latest message
print(self.OtherUserArray)
}
The problem is not where you access self.OtherUserArray, but when you access it.
Data is loaded from Firebase asynchronously, while the rest of you code typically runs synchronously. By the time the print(self.OtherUserArray) in your sendMessage runs, the OtherUserArray.append(otherUserUid) hasn't been executed yet, so the array in empty. You can most easily verify this by running the code in a debugger and setting breakpoints on those like, or by adding print statements and checking the order of their output.
For examples of this, as well as how to solve the problem, see these previous questions:
Why I couldn't assign fetched values from Firestore to an array in Swift?
Is Firebase getDocument run line by line?
Storing asynchronous Cloud Firestore query results in Swift
How do I save data from cloud firestore to a variable in swift?
Why are print statements not executing in correct order?? Am I crazy or is Xcode?
Get all documents at once in a completion handler with getDocuments in Firestore
Related
On an app Im working on, users are able to upload projects by selecting an "upload project" button. Selecting this button triggers a segue to a screen where the user can now input their information for the project. Once the information is filled in and users select "upload", the screen dismisses itself, returning the user to their profile. The problem is that when a user returns to the profile screen, the project they just uploaded does not show in the collection view. it only shows the projects that were downloaded when the user first viewed their profile.
To try and fix this, I added a snapshot listener. The problem with this is: It loads the new project in, but in return, it loads every project twice. How can I set it up to where only the new project is appended, instead of every project being added again?
func setUserProjects(){
let uid = Auth.auth().currentUser?.uid
let db = Firestore.firestore()
let docRef = db.collection("projects")
docRef.whereField("uid", isEqualTo: uid!).getDocuments { (snapshot, err) in
if let err = err {
print("Error:\(err)")
} else {
for document in snapshot!.documents {
var dictionary = document.data()
let project = Project(dictionary: dictionary as [String: AnyObject])
project.id = document.documentID
self.userProjects.append(project)
self.userProjects.uniqued()
}
}
self.projectsCollectionView.reloadData()
}
docRef.whereField("uid", isEqualTo: uid!).addSnapshotListener { snapshot, err in
if let err = err {
print("Error:\(err)")
} else {
for document in snapshot!.documents {
var dictionary = document.data()
let project = Project(dictionary: dictionary as [String: AnyObject])
project.id = document.documentID
///only append the project if it's not already added
if !self.userProjects.contains(project) {
self.userProjects.append(project)
}
}
}
}
}
I want to save the whole json response from api. I tried SQFLITE library to store but i cant able to achieve to store a complete json as it need to store in a table format. I'm very new to flutter. can any body suggest how can i achieve this. Below i'm attaching my sample json for your reference.
{
"result": {
"allitems": {
"answered_status": 0,
"list_items": [
{
"is_answered": false,
"options": [
{
"image_url": "assets/images/orders/jersey.jpg",
"description": "Jersey",
"title": "Jersey",
"price": 23
},
{
"image_url": "assets/images/orders/bat.png",
"description": "Bat",
"title": "Bat",
"price": 5
},
]
}
],
"no_of_items": 12,
"title": "ALL"
}
},
"status_code": 200
}
I was wrong about SharedPreferences in my comment. Turns out SharedPreferences doesn't support Map<dynamic, dynamic> and only up to List<String> yet.
So you can use a database management package sembast made by the same guy who made SQFLite.
You can get help with this link to convert JSON objects to Map and vice-versa.
EDIT -
You can do something like -
import 'package:sembast/sembast.dart';
Map<dynamic, dynamic> sampleMap;
// Skipping this part
sampleMap = // Retrive and convert JSON to Map
// A sample DB in the current directory
String dbPath = 'sample.db';
DatabaseFactory dbFactory = databaseFactoryIo;
// We use the database factory to open the database
Database db = await dbFactory.openDatabase(dbPath);
var store = intMapStoreFactory.store();
// Storing Map to DB
var key = await store.add(db, sampleMap);
// Retrieving values back
var record = await store.record(key).getSnapshot(db);
// From your provided sample JSON in question
var value = record["result"]["allitems"]["list_items"][0]["options"]["image_url"];
print(value);
// value = 'assets/images/orders/jersey.jpg'
Similarly, you can explore the documentation of the package for more data operations.
Convert it to a string, you can store it in shared preference.
import 'dart:convert';
...
var s = json.encode(myMap);
// or var s = jsonEncode(myMap);
json.decode()/jsonDecode() makes a map from a string when you load it.
I can create a new calendar and save it with the following function:
func createCalendarForUser() {
let sourcesInEventStore = self.eventStore.sources
//works but doesn't persist
let subscribedSourceIndex = sourcesInEventStore.index {$0.title == "Subscribed Calendars"}
if let subscribedSourceIndex = subscribedSourceIndex {
let userCalendar = EKCalendar(for: .event, eventStore: self.eventStore)
userCalendar.title = "newCalendar"
userCalendar.source = sourcesInEventStore[subscribedSourceIndex]
do {
try self.eventStore.saveCalendar(userCalendar, commit: true)
print("calendar creation successful")
} catch {
print("cal \(userCalendar.source.title) failed : \(error)")
}
}
}
This functions great while the app is open and running. I can save events to them, i can see them in my local calendar, and life is good. However, once the app is terminated and goes into the background the calendars disappear, along with any events created in them. I've tried saving the calendar to different sources other then the Subscribed Calendars source but when i do that the calendars wont even save in the first place. Heres one of the attempts at using the local source:
func createCalendarForUser() {
let sourcesInEventStore = self.eventStore.sources
//never saves calendar
let localSourceIndex = sourcesInEventStore.index {$0.sourceType == .local}
if let localSourceIndex = localSourceIndex {
let userCalendar = EKCalendar(for: .event, eventStore: self.eventStore)
userCalendar.title = "newCalendar"
userCalendar.source = sourcesInEventStore[localSourceIndex]
do {
try self.eventStore.saveCalendar(userCalendar, commit: true)
print("creating new calendar successful")
} catch {
print("creating new calendar failed : \(error)")
}
}
}
I also tried this method :
func createCalendarForUser() {
let sourcesInEventStore = self.eventStore.sources
//doesnt work
let userCalendar = EKCalendar(for: .event, eventStore: self.eventStore)
userCalendar.title = "newCalendar"
userCalendar.source = sourcesInEventStore.filter{
(source: EKSource) -> Bool in
source.sourceType.rawValue == EKSourceType.local.rawValue
}.first!
do {
try self.eventStore.saveCalendar(userCalendar, commit: true)
print("creating new calendar succesful")
} catch {
print("creating new calendar failed : \(error)")
}
}
As metioned here as mentioned here https://www.andrewcbancroft.com/2015/06/17/creating-calendars-with-event-kit-and-swift/
Has anyone else come across this problem?
The blog post you link do does two things when it saves the calendar, it uses the saveCalendar(, commit) method to save the calendar to the event store, and then also saves the identifier for the calendar to user defaults so that it can be retrieved at a later time:
NSUserDefaults.standardUserDefaults().setObject(newCalendar.calendarIdentifier, forKey: "EventTrackerPrimaryCalendar")
You're doing the first, but not the second step, so your calendars will be persisting in the store, but you're not keeping the information needed to retrieve them in the future.
I recently had an issue with Alamofire (More generally with asynchronous calls)
I have two models, Listings and Users. Listings contains a user's email, and I would also like to get the user's first and last name (I understand I could solve this in the backend as well, however, I would like to see if there is a frontend solution as this problem comes up for something more complicated as well)
Currently I'm making a GET request to get all listings, and I'm looping through them, and making another GET request to get firstname, lastname.
I need to wait to get the result of this get request, or at the minimum append it to my listings dictionary. Likewise, before I do anything else (Move on to the next screen of my app), I'd like to have all the listings be linked to a firstname, lastname. Because theres a loop, this specifically seems to cause some issues (ie if it was just two nested GET requests, it could be in a callback). Is there an easy way to get around this. I've attached psuedocode below:
GET Request to grab listings:
for each listing:
GET request to grab first_name, last_name
Once all listings have gotten first_name, last_name -> Load next page
The answer for your question is called a dispatch group
Dispatch groups can be entered and left only to execute some code when no code is currently inside the dispatch group.
GET Request to grab listings{
var downloadGroup = dispatch_group_create()
//Create a dispatch group
for each listing{
dispatch_group_enter(downloadGroup)
//Enter the dispatch group
GET request to grab first_name, last_name (Async){
dispatch_group_leave(downloadGroup)
//Leave the dispatch group
}
}
dispatch_group_notify(downloadGroup, dispatch_get_main_queue()) {
//Run this code when all GET requests are finished
}
}
As demonstrated by this code.
Source and interesting reading material about dispatching: Grand Central Dispatch Tutorial for Swift by Ray Wenderlich
With Scala-style futures and promises you can do something like this:
let future: Future<[Listing]> = fetchListings().flatMap { listings in
listings.traverse { listing in
fetchUser(listing.userId).map { user in
listing.userName = "\(user.firstName) \(user.lastName)"
return listing
}
}
}
The result of the above expression is a future whose value is an array of listings.
Print the listing's user name, once the above expression is finished:
future.onSuccess { listings in
listings.forEach {
print($0.userName)
}
}
Scala-style future and promise libraries: BrightFutures or FutureLib
Below a ready-to-use code example which you can paste into a playgrounds file to experiment with any of the above libraries (works in FutureLib, BrightFutures might require slight modifications).
import FutureLib
import Foundation
import XCPlayground
XCPlaygroundPage.currentPage.needsIndefiniteExecution = true
class Listing {
let userId: Int
init(userId: Int) {
self.userId = userId
userName = ""
}
var userName: String
}
struct User {
let id: Int
let firstName: String
let lastName: String
init (_ id: Int, firstName: String, lastName: String) {
self.id = id
self.firstName = firstName
self.lastName = lastName
}
}
func fetchListings() -> Future<[Listing]> {
NSLog("start fetching listings...")
return Promise.resolveAfter(1.0) {
NSLog("finished fetching listings.")
return (1...10).map { Listing(userId: $0) }
}.future!
}
// Given a user ID, fetch a user:
func fetchUser(id: Int) -> Future<User> {
NSLog("start fetching user[\(id)]...")
return Promise.resolveAfter(1.0) {
NSLog("finished fetching user[\(id)].")
return User(id, firstName: "first\(id)", lastName: "last\(id)")
}.future!
}
let future: Future<[Listing]> = fetchListings().flatMap { listings in
listings.traverse { listing in
fetchUser(listing.userId).map { user in
listing.userName = "\(user.firstName) \(user.lastName)"
return listing
}
}
}
future.onSuccess { listings in
listings.forEach {
print($0.userName)
}
}
Here's a possible solution:
GET Request to grab listings:
var n = the number of listings
var i = 0 //the number of retrieved
for each listing:
GET request to grab first_name, last_name, callback: function(response){
assign first/last name using response.
i+=1
if(i==n)
load_next_page()
}
So what this does is keep a counter of how many firstname/lastname records you've fetched. Make sure that you handle cases where a call to get the name fails.
Or, as suggested in a comment on the question, you could use promises. They make async code like this much nicer.
I have a class called Chat. Its objects have two attributes: "users" and "messages". I want to detect if a message was added to the "messages" array and if so send push notification to all users from "users" array.
This is my code:
Parse.Cloud.beforeSave(„Chat”, function(request, response) {
if (request.object.dirty("messages")) {
var users = request.object.get("users");
for (var i=0; i<users.length; i++) {
var currentUserId = users[i].get("objectId");
console.log(currentUserId);
}
response.success();
} else {
response.error(„no messages sent”);
}
});
I know how to send push but I have a problem with getting the PFUser objects from "users" array. When I try to print their IDs in console I get "no message sent" error in parse.com logs. Can anyone help me ?