Real-time data delivery is one of the most important elements of mobile application developing. Primarily for the reason that users want to see up-to-date data in the UI without any additional actions/gestures to update the information. This capability is provided out of the box in Backendless and we call it Real-Time Database. Applications using Backendless SDK for iOS can subscribe to changes happening in a data table in the database. This quick guide will help you to get an understanding how Backendless real-time database works. You will build a basic iOS app displaying a list of movies. The movie list will be dynamically updated any time when the data changes in the database. The changes in the database which will be displayed in the application in real-time include: new movies saved in the database, existing movies modified or deleted.
The sample application developed as a part of this quick start guide requires some data to be stored in the database. This section of the guide contains instructions for populating the database with sample data. There are several ways for getting data into Backendless database, however, in this guide we will consider only two methods, both of them use Backendless Console. The first one imports data from a CSV file, the second one uses REST Console:
Create a file called Movies.csv with the following content:
"description(STRING(100))","rating(STRING(36))","release_year(STRING(36))","title(STRING(36))","objectId(STRING_ID)","ownerId(STRING(36))","created(DATETIME)","updated(DATETIME)" "A Intrepid Documentary of a Sumo Wrestler And a Astronaut who must Battle a Composer in The Outback","G","2006","CROSSROADS CASUALTIES","2AF53B19-6A91-0F28-FF05-85980DFEE500",null,"Tue Jul 02 08:26:30 UTC 2019",null "A Thoughtful Tale of a Woman And a A Shark who must Conquer a Dog in A Monastery","NC-17","2006","JASON TRAP","56B0EBD9-3C2B-68D6-FFFB-7EE98388A000",null,"Tue Jul 02 08:26:30 UTC 2019",null "A Touching Drama of a Crocodile And a Crocodile who must Conquer a Explorer in Soviet Georgia","G","2006","DESTINY SATURDAY","81F71483-A809-4662-FF43-951511680000",null,"Tue Jul 02 08:26:30 UTC 2019",null "A Epic Drama of a Mad Scientist And a Explorer who must Succumb a Waitress in An Abandoned Fun House","PG","2006","SKY MIRACLE","D1040D67-A9E8-72C0-FF4B-88E219616A00",null,"Tue Jul 02 08:26:30 UTC 2019",null "A Action-Packed Reflection of a Car And a Moose who must Outgun a Car in A Shark Tank","R","2006","PLUTO OLEANDER","D9B7498B-CF23-6D70-FFB8-0AA56859C000",null,"Tue Jul 02 08:26:30 UTC 2019",null
Open Backendless Console and navigate to Manage -> Import section and import the file as described on the screenshot below:
Click the IMPORT button. This will add the data into the Movies data table. The name of the table in the database is the same as the name of the CSV file.
Open Backendless Console and click the Data icon. Create a new data table with the name “Movies”:
You will be prompted to create columns in the new data table. Cancel out as the columns will be created dynamically with the following instructions. Click the REST Console tab. You will make a single API call which will create data table schema and will save some sample data for the app:
[ { "title": "JASON TRAP", "release_year": "2006", "rating": "NC-17", "description": "A Thoughtful Tale of a Woman And a A Shark who must Conquer a Dog in A Monastery" }, { "title": "SKY MIRACLE", "release_year": "2006", "rating": "PG", "description": "A Epic Drama of a Mad Scientist And a Explorer who must Succumb a Waitress in An Abandoned Fun House" }, { "title": "CROSSROADS CASUALTIES", "release_year": "2006", "rating": "G", "description": "A Intrepid Documentary of a Sumo Wrestler And a Astronaut who must Battle a Composer in The Outback" }, { "title": "PLUTO OLEANDER", "release_year": "2006", "rating": "R", "description": "A Action-Packed Reflection of a Car And a Moose who must Outgun a Car in A Shark Tank" }, { "title": "DESTINY SATURDAY", "release_year": "2006", "rating": "G", "description": "A Touching Drama of a Crocodile And a Crocodile who must Conquer a Explorer in Soviet Georgia" } ]
You can verify that the data has been stored in the database by clicking the DATA BROWSER tab:
Create an Xcode project, close Xcode and then install the BackendlessSwift
framework using cocoapods:
pod 'BackendlessSwift'
Then open the created .xcworkspace file and design the storyboard to look as shown below:
Movies View Controller
is UITableViewController
which represents the Movie
objects. Create the MovieCell
class for displaying movie’s information:
class MovieCell: UITableViewCell { @IBOutlet weak var titleLabel: UILabel! @IBOutlet weak var descLabel: UILabel! @IBOutlet weak var ratingLabel: UILabel! @IBOutlet weak var yearLabel: UILabel! @IBOutlet weak var createdLabel: UILabel! override func awakeFromNib() { super.awakeFromNib() } override func setSelected(_ selected: Bool, animated: Bool) { super.setSelected(selected, animated: animated) } }
@interface MovieCell : UITableViewCell @property (weak, nonatomic) IBOutlet UILabel *titleLabel; @property (weak, nonatomic) IBOutlet UILabel *descLabel; @property (weak, nonatomic) IBOutlet UILabel *ratingLabel; @property (weak, nonatomic) IBOutlet UILabel *yearLabel; @property (weak, nonatomic) IBOutlet UILabel *createdLabel; @end
Movies
class to represent Movies objects:@objcMembers class Movies: NSObject { var objectId: String? var title: String? var desc: String? var rating: String? var release_year: String? var created: Date? }
@interface Movies : NSObject @property (strong, nonatomic) NSString *objectId; @property (strong, nonatomic) NSString *title; @property (strong, nonatomic) NSString *desc; @property (strong, nonatomic) NSString *rating; @property (strong, nonatomic) NSString *release_year; @property (strong, nonatomic) NSDate *created; @end
MoviesViewController
class:import Backendless class MoviesViewController: UITableViewController { private let API_HOST = "https://api.backendless.com" private let APP_ID = "YOUR_APP_ID" private let API_KEY = "YOUR_API_KEY" private var moviesList = [Movies]() private var moviesStore: DataStoreFactory = Backendless.shared.data.of(Movies.self) override func viewDidLoad() { super.viewDidLoad() initBackendless() enableRealTime() getMovies() } func initBackendless() { Backendless.shared.hostUrl = API_HOST Backendless.shared.initApp(applicationId: APP_ID, apiKey: API_KEY) // we need this mapping because the description Swift property cannot be overridden moviesStore.mapColumn(columnName: "description", toProperty: "desc") } func getMovies() { let queryBuilder = DataQueryBuilder() queryBuilder.setPageSize(pageSize: 100) queryBuilder.setSortBy(sortBy: ["created"]) moviesStore.find(queryBuilder: queryBuilder, responseHandler: { [weak self] foundMovies in if let foundMovies = foundMovies as? [Movies] { self?.moviesList = foundMovies self?.tableView.reloadData() } }, errorHandler: { [weak self] fault in self?.showError(fault: fault) }) } func showError(fault: Fault) { let alert = UIAlertController(title: "Error", message: fault.message ?? "An error occurred", preferredStyle: .alert) alert.addAction(UIAlertAction(title: "Dismiss", style: .cancel, handler: nil)) self.present(alert, animated: true, completion: nil) } func enableRealTime() { // we will add code for Real Time Database here } override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return moviesList.count } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let movie = moviesList[indexPath.row] let dateFormatter = DateFormatter() dateFormatter.dateFormat = "E MMM d yyyy" let cell = tableView.dequeueReusableCell(withIdentifier: "MovieCell", for: indexPath) as! MovieCell cell.titleLabel.text = movie.title ?? "-" cell.descLabel.text = movie.desc ?? "-" cell.ratingLabel.text = movie.rating ?? "-" cell.yearLabel.text = movie.release_year ?? "-" if let created = movie.created { cell.createdLabel.text = "added " + dateFormatter.string(from: created) } return cell } }
#define API_HOST @"https://api.backendless.com" #define APP_ID @"YOUR_APP_ID" #define API_KEY @"YOUR_API_KEY" @interface MoviesViewController() { NSMutableArray *moviesList; DataStoreFactory *moviesStore; } @end @implementation MoviesViewController - (void)viewDidLoad { [super viewDidLoad]; moviesList = [NSMutableArray new]; moviesStore = [Backendless.shared.data of:[Movies class]]; [self initBackendless]; [self enableRealTime]; [self getMovies]; } - (void)initBackendless { Backendless.shared.hostUrl = API_HOST; [Backendless.shared initAppWithApplicationId:APP_ID apiKey:API_KEY]; // we need this mapping because the description Swift property cannot be overridden [moviesStore mapColumnWithColumnName:@"description" toProperty:@"desc"]; } - (void)getMovies { DataQueryBuilder *queryBuilder = [DataQueryBuilder new]; [queryBuilder setPageSizeWithPageSize:100]; [queryBuilder setSortBySortBy:@[@"created"]]; [moviesStore findWithQueryBuilder:queryBuilder responseHandler:^(NSArray *foundMovies) { self->moviesList = [NSMutableArray arrayWithArray:foundMovies]; [self.tableView reloadData]; } errorHandler:^(Fault *fault) { [self showError:fault]; }]; } - (void)showError:(Fault *)fault { UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Error" message:fault.message preferredStyle:UIAlertControllerStyleAlert]; [alert addAction:[UIAlertAction actionWithTitle:@"Dismiss" style:UIAlertActionStyleCancel handler:nil]]; [self presentViewController:alert animated:YES completion:nil]; } - (void)enableRealTime { // we will add code for Real Time Database here } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return moviesList.count; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { Movies *movie = [moviesList objectAtIndex:indexPath.row]; NSDateFormatter *dateFormatter = [NSDateFormatter new]; dateFormatter.dateFormat = @"E MMM d yyyy"; MovieCell *cell = [tableView dequeueReusableCellWithIdentifier:@"MovieCell" forIndexPath:indexPath]; cell.titleLabel.text = movie.title; cell.descLabel.text = movie.desc; cell.ratingLabel.text = movie.rating; cell.yearLabel.text = movie.release_year; cell.createdLabel.text = [@"added " stringByAppendingString:[dateFormatter stringFromDate:movie.created]]; return cell; } @end
The class starts with the method which initializes Backendless SDK:
private let API_HOST = "https://api.backendless.com" private let APP_ID = "YOUR_APP_ID" private let API_KEY = "YOUR_API_KEY" func initBackendless() { Backendless.shared.hostUrl = API_HOST Backendless.shared.initApp(applicationId: APP_ID, apiKey: API_KEY) // we need this mapping because the description Swift property cannot be overridden Backendless.shared.data.of(Movies.self).mapColumn(columnName: "description", toProperty: "desc") }
#define API_HOST @"https://api.backendless.com" #define APP_ID @"YOUR_APP_ID" #define API_KEY @"YOUR_API_KEY" - (void)initBackendless { Backendless.shared.hostUrl = API_HOST; [Backendless.shared initAppWithApplicationId:APP_ID apiKey:API_KEY]; // we need this mapping because the description Swift property cannot be overridden [moviesStore mapColumnWithColumnName:@"description" toProperty:@"desc"]; }
There are two functions to render Movies objects in UI:
tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section; - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;
The following line of code obtains a reference to the Movies
data table:
var moviesStore: DataStoreFactory = Backendless.shared.data.of(Movies.self)
DataStoreFactory *moviesStore = [Backendless.shared.data of:[Movies class]];
let queryBuilder = DataQueryBuilder() queryBuilder.setPageSize(pageSize: 100) queryBuilder.setSortBy(sortBy: ["created"]) moviesStore.find(queryBuilder: queryBuilder, responseHandler: { [weak self] foundMovies in if let foundMovies = foundMovies as? [Movies] { self?.moviesList = foundMovies self?.tableView.reloadData() } }, errorHandler: { [weak self] fault in self?.showError(fault: fault) })
DataQueryBuilder *queryBuilder = [DataQueryBuilder new]; [queryBuilder setPageSizeWithPageSize:100]; [queryBuilder setSortBySortBy:@[@"created"]]; [moviesStore findWithQueryBuilder:queryBuilder responseHandler:^(NSArray *foundMovies) { self->moviesList = [NSMutableArray arrayWithArray:foundMovies]; [self.tableView reloadData]; } errorHandler:^(Fault *fault) { [self showError:fault]; }];
moviesList
. The moviesList
variable is important for real-time integration, we will come to it a little bit later. And also when data is loaded, it is rendered. In case if an error occurs, we will catch it and also render in the UI. The enableRealTime
function is left empty for now. With the code we have added by now you can already experience the app. To do this build and run the project. You should see the following screen:
Before we continue, let’s review what Backendless Real Time Database is. In most cases, you want your application to display the up-to-date data without any additional user actions (such as pull to refresh for instance) or any additional requests to the server. In Backendless Real Time Database there are three main events, which, once occurred, get your clients app synchronized with the server data:
All our SDKs (JS, Android, iOS and .NET) include necessary logic responsible for establishing and maintaining a connection to our real-time servers. The SDKs also provide the API to subscribe to the events listed above. For more information see the Real-Time Database API documentation.
Now we got to the final part of the guide – adding real-time subscriptions to the code. When a new database record in the Movies
table is created, or an existing one is updated or deleted, we want the app to be notified about it and re-render the UI. Additionally, we need to maintain an up-to-date list in the app. For this purpose the code uses the moviesList
variable. The list of the Movie
objects in the UI will correspond to the objects in moviesList
variable. To add the real-time integration, modify the enableRealTime
function as shown below:
func enableRealTime() { let rtHandlers = moviesStore.rt let _ = rtHandlers?.addCreateListener(responseHandler: { [weak self] movie in if let movie = movie as? Movies { self?.moviesList.append(movie) self?.tableView.reloadData() } }, errorHandler: { [weak self] fault in self?.showError(fault: fault) }) let _ = rtHandlers?.addUpdateListener(responseHandler: { [weak self] movie in if let movie = movie as? Movies, let existingMovie = self?.moviesList.first(where: { $0.objectId == movie.objectId }), let index = self?.moviesList.firstIndex(of: existingMovie) { self?.moviesList[index] = movie self?.tableView.reloadData() } }, errorHandler: { [weak self] fault in self?.showError(fault: fault) }) let _ = rtHandlers?.addDeleteListener(responseHandler: { [weak self] movie in if let movie = movie as? Movies, let existingMovie = self?.moviesList.first(where: { $0.objectId == movie.objectId }), let index = self?.moviesList.firstIndex(of: existingMovie) { self?.moviesList.remove(at: index) self?.tableView.reloadData() } }, errorHandler: { [weak self] fault in self?.showError(fault: fault) }) }
- (void)enableRealTime { EventHandlerForClass *rtHandlers = moviesStore.rt; RTSubscription *createSubscr = [rtHandlers addCreateListenerWithResponseHandler:^(Movies *movie) { [self->moviesList addObject:movie]; [self.tableView reloadData]; } errorHandler:^(Fault *fault) { [self showError:fault]; }]; RTSubscription *updateSubscr = [rtHandlers addUpdateListenerWithResponseHandler:^(Movies *movie) { NSPredicate *predicate = [NSPredicate predicateWithFormat:@"objectId == %@", movie.objectId]; Movies *existingMovie = [self->moviesList filteredArrayUsingPredicate:predicate].firstObject; NSInteger index = [self->moviesList indexOfObject:existingMovie]; [self->moviesList setObject:movie atIndexedSubscript:index]; [self.tableView reloadData]; } errorHandler:^(Fault *fault) { [self showError:fault]; }]; RTSubscription *deleteSubscr = [rtHandlers addDeleteListenerWithResponseHandler:^(Movies *movie) { NSPredicate *predicate = [NSPredicate predicateWithFormat:@"objectId == %@", movie.objectId]; Movies *existingMovie = [self->moviesList filteredArrayUsingPredicate:predicate].firstObject; [self->moviesList removeObject:existingMovie]; [self.tableView reloadData]; } errorHandler:^(Fault *fault) { [self showError:fault]; }]; }
createListener
– new movie record is pushed into moviesList and the UI is re-renderedupdateListener
– the updated movie object, which is the argument of the listener function, replaces one in moviesList and the UI is re-rendereddeleteListener
– the deleted movie object is removed from movieList and the UI is re-renderedLet’s check what we’ve got:
To experience real-time database integration:
That’s all folks, happy coding!