UITableView shows up as blank before data is loaded from Firebase - ios

I’ve created simple UITableView with couple of rows that is loading from Firebase.
On viewWillAppear I connect observer and populate the array with data.
The problem happens when view is already loaded but asynchronous Firebase callback is still wasn’t called - Table appears as blank.
The table refreshes after callback and looks like expected but the delay is still noticeable.
I’ve configured offline mode and expecting that observer’s handler should be called immidiately but it doesn’t.
What is the proper way to handle that?
Waiting bar looks like not the best option because it’s just 100ms or so and data is already on device and I have just 8 rows.
Is there any solution?
Thank you!
Observe code:
-(void) viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
refHandle = [areasRef observeEventType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot * _Nonnull snapshot) {
[self.arrayOfAreas removeAllObjects];
for (FIRDataSnapshot* areaSnapshot in snapshot.children){
Area *area = [[Area alloc] init];
[area retrieveFromSnapshot:areaSnapshot];
[self.arrayOfAreas addObject:area];
} [self.tableView reloadData];
}]; }
viewDidLoad:
- (void)viewDidLoad {
[super viewDidLoad];
self.ref = [[FIRDatabase database] reference];
FIRUser *currentUser = [FIRAuth auth].currentUser;
areasRef = [[[_ref child:#"users"] child:currentUser.uid] child:#"areas"];
self.tableView.dataSource = self;
self.tableView.delegate = self;
self.tableView.tableFooterView = [[UIView alloc]init];
self.arrayOfAreas = [NSMutableArray new];
self.tableView.allowsMultipleSelectionDuringEditing = NO; }

After fresh install of your app,
Obviously there will be a delay to fetch the datas from firebase.
once you fetch all the data, you have to save it in your local DB.
From second time onwards, show the datas from your local DB and concurrently fetch the firebase DB also. (if you doesn't do update/delete in inner key values of the fetching path, firebase DB fetching query can be optimised based on the local data).
If you are saving the local DB datas to arrayOfAreas. you can change the observe block as follows
NSMutableArray *arrayTemp = [[NSMutableArray alloc]init];
for (FIRDataSnapshot* areaSnapshot in snapshot.children){
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Area" inManagedObjectContext:[CoreData sharedData].context];
Area *area = [[Area alloc] initWithEntity:entity insertIntoManagedObjectContext:nil];
[area retrieveFromSnapshot:areaSnapshot];
[arrayTemp addObject:area];
}
/**
if you are fetching all datas again
*/
self.arrayOfAreas = arrayTemp;
[self.tableView reloadData];
/**
if you are fetching only the datas that are newly added (datas that are not existing in local DB)
*/
[self.arrayOfAreas addObjectsFromArray: arrayTemp];
[self.tableView reloadData];
if you are using firebase offline feature, try below way. (it may not be a perfect solution)
invoke [FIRDatabase database].goOffline in viewDidLoad or app launch.
once the observer block executed, [FIRDatabase database].goOnline.

Your UITableview is getting loaded on viewDidLoad, as there would be no data in you array at initially your tableView is blank.
If you don't wish to load your tableView until the you retrieve data then you may assign your datasource and delegate after you finish fetching your records. (In your completion block of Firebase Method)
You can show loader till your data is retrieved.

Related

Accessing Realm property on main thread crashes

I am trying to fetch realm objects and store them in a dictionary with key being a date string.
I start fetching the objects in a realm queue
//im doing this as data fetch is taking too much time
dispatch_async( [[ActivityManager sharedInstance]getRealmQueue], ^{
[self getActivitiesForStartDate:_startDate endDate:_endDate];
});
what getActivitiesForStartDate does is:
Get array of dates
For each date in the array get the realm objects to show on tableview
RLMResults *activityResults = [[[self getActivitySource]activities] objectsWhere:#"my predicate string"];
and enumerate through the fetched activities, and add them to an array based on condition
for (RealmActivity *tempActivity in tempActArr){
if (tempActivity.startTime) {
//condition met
[dataArr addObject:#{#"activity":tempActivity,
#"occurence":selectedDate}];
}else{
[dataArr addObject:#{#"activity":tempActivity,
#"occurence":tempActivity.startTime}];
}
}
after the data fetch is done, I'm trying to load the realm objects on tableview.
However when I try to access Realm object in cellForRow method, it crashes the app saying trying to access Realm from incorrect thread:
- (void)configureCell:(TaskListViewCell *)cell forIndexPath:(NSIndexPath *)indexPath
{
NSArray *ar = self.dataDictionary[[[self.monthView dateSelected]shortDateString]];
RealmActivity *activity = ar[indexPath.section];
cell.dateTime.text = activity.Type; <- crashes here
cell.regarding.text = activity.regarding;
cell.location.text =activity.location;
}
I see that configureCell is getting called on main thread, but how can I use Realm properly to avoid crashes like these ?
I have no idea how to use RLMThreadSafeReference for preparing datasource.

FEventTypeValue running before FEventTypeChildAdded

I'm looking at the Firechat demo project, and they use a technique whereas they're using FEventTypeChildAdded as well as FEventTypeValue as the latter will fire just after FEventTypeChildAdded. This is done in order to prevent a "spam" of FEventTypeChildAdded callbacks to avoid a UI lockup. However, I implemented the same technique in my app, but FEventTypeValue is actually called before FEventTypeChildAdded, resulting in the BOOL changing to NO and locking the UI.
From the demo project:
// This allows us to check if these were messages already stored on the server
// when we booted up (YES) or if they are new messages since we've started the app.
// This is so that we can batch together the initial messages' reloadData for a perf gain.
__block BOOL initialAdds = YES;
[self.firebase observeEventType:FEventTypeChildAdded withBlock:^(FDataSnapshot *snapshot) {
// Add the chat message to the array.
if (newMessagesOnTop) {
[self.chat insertObject:snapshot.value atIndex:0];
} else {
[self.chat addObject:snapshot.value];
}
// Reload the table view so the new message will show up.
if (!initialAdds) {
[self.tableView reloadData];
}
}];
// Value event fires right after we get the events already stored in the Firebase repo.
// We've gotten the initial messages stored on the server, and we want to run reloadData on the batch.
// Also set initialAdds=NO so that we'll reload after each additional childAdded event.
[self.firebase observeSingleEventOfType:FEventTypeValue withBlock:^(FDataSnapshot *snapshot) {
// Reload the table view so that the intial messages show up
[self.tableView reloadData];
initialAdds = NO;
}];

Populate a UITableView with data retrieved from a NSURLConnection

Basically I have a main screen that has a button on it, when you click on that button I want to load a list of users from a server and display them on the next screen in the tableview.
I can get the data with no issues, and pass it to my tableview with no issues - my problem lies with loading the data into the cells after I have received the data!
Processes exist like this:
Tap button
Starts NSURLConnection
Opens up UITableView on screen
Loads nothing
Data returns, adds to NSArray
Tableview Reload
In viewWillAppear - make local users NSArray equal received Data
Nothing loads.
If I then press back, then press the button again, all my cells are populated with the data I received before.
Thanks in advance for any help. I've been searching around for a while now :(
Edit:
When moving from main screen to the tableview
Note: getUsers sets up the NSURLConnection and starts the connection
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
if ([segue.identifier isEqualToString:#"selectUser"]) {
DDNetworkRequest *networkRequest = [[DDNetworkRequest alloc] init];
[networkRequest getUsers];
}
}
Tableview class:
Note: returnUserList just returns the array of data which is set as a variable within the network class
-(void)viewWillAppear:(BOOL)animated{
DDNetworkRequest *networkRequest = [[DDNetworkRequest alloc] init];
users = [networkRequest returnUserList];
}
Network Class:
note: returnUsers manipulates and then saves the NSArray variable
-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{
NSDictionary *jsonDictionary;
jsonDictionary = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:nil];
[usersJSONDict isEqualToDictionary: jsonDictionary];
[self returnUsers:jsonDictionary];
DDUserMessage *userMessageTable = [[DDUserMessage alloc] init];
[userMessageTable.tableView reloadData];
}
** Edit2 **
returnUser method
-(void) returnUsers:(NSDictionary*)userDict{
userListArray = [userDict valueForKey:#"username"];
}
Without any code provided from your side, I guess u forgot this awesome operation inside the HTTP get request success block:
[self.tableView loadData];
M I RIGHT? ;)
After you receive the data, you have to pass it to the data source array, then call
[self.tableView reloadData];
Also make sure you call this line from the main thread. You cannot make changes to UI from another thread.

Table not updated after addition

I want my app to check the core data store at start-up. If the store is empty, it will add two items into it. What is the best way to implement this?
Can I put the following code in viewDidLoad?
- (void)viewDidLoad
{
[super viewDidLoad];
NSManagedObjectContext *managedObjectContext = [self managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] initWithEntityName:#"MonitorItem"];
self.monitorItemArray = [[managedObjectContext
executeFetchRequest:fetchRequest
error:nil] mutableCopy];
// If the core data is empty, populate it with the two compulsory items
if ([self.monitorItemArray count] == 0)
{
self.AddMandatoryItems;
}
[self.tableView reloadData];
}
I have searched other articles but none seems to give me an answer that I could understand.
Getting information from managedObjectContext in viewDidLoad is good.
If in cellForRowAtIndexPath you populate the cells from self.monitorItemArray than there is no reason to call reloadData (Which essentially erase the entire table view and re-draw it from scratch - which is exactly what happens when the view is appearing on screen any way...).
If you also show information from a web service, you can call reloadData in the response method to replace the existing data with the one that came from the web. Otherwise - if only information from core data is shown on the table view - no need for reloadData (Or maybe only in a case where the information in your managedObjectContext has changed while the table view is on screen).

Load only tagged items from plist to UITableView

Is there a specific way to load only certain tagged items from a plist file to a UITableView? Something like: load this item into a cell if the property "Tag" contains "transportation" in it, keeping in mind that the property "Tag" will will be string with "transportation, car, train, bus, etc."
Because of what I'm working with (HTML data loading in UIWebViews), I haven't found a method that allows me to use true search (without paying), so I was planning on implementing a tableview with all "searchable" keywords which will then load only certain items into the tableview, form which the user can get more information in the form of webview.
I figured out how to use NSPredicates and loaded the table.
(void)viewWillAppear:(BOOL)animated
{
// Refresh the contents of the table whenever this screen becomes visible
self.favReps = [[[NSUserDefaults standardUserDefaults] objectForKey:#"favReps"]mutableCopy];
NSString *contentFile = [[NSBundle mainBundle] pathForResource:#"cf10" ofType:#"plist"];
NSArray *Contents = [[NSArray alloc] initWithContentsOfFile:contentFile];
NSPredicate *inPredicate = [NSPredicate predicateWithFormat:#"Section IN%#", favReps];
favRepsDict = [[[NSMutableArray alloc] initWithArray:[Contents filteredArrayUsingPredicate:inPredicate]] mutableCopy];
[super viewWillAppear:animated];
[self.tableView reloadData];
}
This will update every time you come back to the favorites screen.

Resources