A tutorial to build an Ionic app with offline support.
Introduction
This is part 2 and advanced part of the Building a Notes App with Offline Support tutorial. This part starts where the basic tutorial ended. Make sure you first complete the basic tutorial.
Step 10: Showing Network Status
In this step, you will add a label to show network status (online/offline) in the app header.
To show network state:
- Open Index page.
- Specify Text of Header Status: {{status}}.
- Add showState scope function.
Apperyio.get("AppClientGetState")({}).then(
function(success){
$scope.status = success.state;
$scope.$apply();
},
function(error){
console.log(error);
},
function(notify){
console.log(error);
});
- Edit init scope function.
$scope.visionStatus = {};
$scope.showState();
You can run now the app with the Internet on and without an Internet connection. The State of the network should be displayed in Header properly. Try to change the network status when the application is working. Status is not changing. Let’s add this capability.
- Add state change notification code at end of showState scope function:
Apperyio.get("AppClientGetState")({}).then(
function(success) {
$scope.status = success.state;
$scope.$apply();
},
function(error) {
console.log(error);
},
function(notify) {
console.log(error);
});
Apperyio.get("mssdk")().then(function(AppClientInstance) {
//subscribe to AppClient state updates
AppClientInstance.on("statechange", function(currentState) {
$scope.status = currentState;
if (currentState == "sync_failed") {
Apperyio.navigateTo("Conflict");
}
$scope.$apply();
});
});
Download and Resources
- Download Ionic app backup file from this step.
Checking Network Status
If you look at the Network tab in the Chrome browser Developer Tools, you see all the service requests when going from Notes to Note page.
Step 11: Adding Auto-login
In this step, you are going to add auto-login functionality. If you logged into the app before and launching the app after a period of no activity, the app will perform and auto-login without prompting the user for credentials.
- In the project tree open Services, and AppClientSettings making sure that autoLogin setting equals true.
- Create a new page and name it Loading.
- Mark Loading page as default in Routing.
- Add load scope function.
Apperyio.get("AppClientService").isUserLoggedIn().then(function(result) {
var page = result ? "Notes" : "Login";
Apperyio.navigateTo(page);
$scope.$apply();
});
- Edit init scope function of Loading page.
$scope.load();
- Auto-login with social networks
To enable auto-login with social-login together to use following code in success social login callback where token is social network login token
Apperyio.get("AppClientLogin")({
"token": token
}).then(
function(success) { // success callback
},
function(error) { // callback to handle request error
});
Result in This Step
The app now supports logging in without prompting for user credentials.
Download and Resources
- Download Ionic app backup file from this step.
Step 12: Adding a Logout
In this step, you are going to add a logout functionality.
To add a menu to app with logout option:
- Add a button to left part of Header of the index page.
- Remove the Text value.
- Set icon to ionic-navigation-round.
- Add the menu-toggle property with empty value.
- In the navigation bar (above the canvas), click the Page link.
- Set the Side menu attribute to True.
- Add a button inside menu:
- Text Logout.
- Add ng-click with the logout() value.
- Add the menu-close attribute with an empty value.
- Add the logout scope function to index page:
Apperyio.get("AppClientLogout")({}).then(
function(success) {
Apperyio.navigateTo("Login");
$scope.$apply();
},
function(error) {
console.log(error);
},
function(notify) {
console.log(notify);
});
- Open the Login page in Design view.
- Set Show Header to False.
Result in This Step
The app now has login and logout functionality.
Download and Resources
- Download Ionic app backup file from this step.
Emulating Offline State
To emulate offline state in Chrome browser, you can use Chrome Offline mode feature.
Step 13: Adding a Conflict Page
In this step, you will add a conflict page to the app which will display conflict situation which can happen after moving from offline to online state.
To add the conflict page and make it display the actions performed offline:
- Add page Conflict.
- Add a text
- Text: Error: {{errorMessage}}.
- Add a text
- Text: Operation : {{operation}}.
- Add a text
- Text: Previous content: {{prevContent}}.
- ng-show: operation != "CREATE".
- Add a text
- Text: Server content: {{serverContent}}.
- ng-show: conflictCode == "AE104".
- Add an input
- ng-model: newContent.
- ng-show: operation != "DELETE".
- Add a button:
- Text: Resolve.
- ng-click: resolve().
- Add the resolve scope function
Apperyio.get("AppClientResolveConflict")({
"action": "UPDATE",
"data": {
"content": $scope.newContent
}
}).then(
function(success) {
alert("Conflict was resolved successfully");
},
function(error) {
console.log(error)
},
function(notify) {
});
- Add a button:
- Text: Delete Conflict.
- ng-click: deleteConflict().
- Add the deleteConflict scope function
Apperyio.get("AppClientResolveConflict")({
"action": "DELETE"
}).then(
function(success) {
alert("Conflict was deleted");
},
function(error) {
console.log(error)
},
function(notify) {
});
- Add a button:
- Text: Retry.
- ng-click: retry().
- Add the retry scope function
Apperyio.get("AppClientRetrySync")({}).then(
function(success) {
Apperyio.navigateTo("Notes");
$scope.$apply();
},
function(error) {
console.log(error)
},
function(notify) {
});
- Add a button:
- Text: Reset.
- ng-click: reset().
- Add the reset scope function
Apperyio.get("AppClientResetFailedSync")({}).then(
function(success) {
Apperyio.navigateTo("Notes");
$scope.$apply();
},
function(error) {
console.log(error);
},
function(notify) {}
);
- Add a button:
- Text: Notes.
- navigate-to: Notes.
Result in This Step
- The app now has a history page to show all operations performed when offline.
Download and Resources
- Download Ionic app backup file from this step.
Step 14: Conflict sutation
To emulate conflict situation in ApperyNote app the unique index will be created for content field and after that, the attempt to insert a duplicate value into the content field will be prohibited (create rest service will return 400 HTTP status with the corresponding message).
- Open Databases > ApperyNoteDB > Notes > Manage indexes.
- Check unique.
- Check note (After that it will not be possible to insert two records with the same note into collection Notes ).
- Insert two records with the same note into collection Notes.
Result in This Step
If you try to add two records with the same note text into the Notes collection or update already existing note to the text of another existing one, the app will show the message that non-unique values are not allowed.
Step 15: Creating a Conflict in Offline Mode
To create a conflict in the offline mode and to make sure that information is not lost:
- Go offline.
- Create a duplicating note.
- Go online.
Resetting Local Changes
Revert operation reverts all local changes made in offline mode without clearing all cached data. Possible reason for this can be failed synchronization, conflicted objects etc. It is recommended to use this operation in general cases.
Reset operation clears everything: cache and local history. It is recommended to use this only when absolutely necessary.
Step 16: Incremental Synchronization
The algorithm to implement incremental synchronization mechanism.
- Set sorting for the model by modifiedAt field.
- Add new field deleted in the Notes collection. Now the records will never be deleted from the DB: they will just be marked as deleted. Actually, all business fields after the delete operation can be empty (we need only _id, modifiedAt and deleted fields).
- Remove the Delete operation for the model.
- Add filtering in App for records which are marked as deleted and do not display them.
To add this feature to the app:
- Add a new column(type Boolean) in Notes collection, call it deleted.
- Open API Express and the ApperyNoteProject project.
- Click edit link notes.
- Press Include for deleted field.
- Set sorting (Sort column) for _updatedAt field (asc).
- Update remove scope function of Notes page.
Tip
If some note(s) are deleted (not marked as deleted) from the database it can because of out of sync. data and malfunction of the app. To solve such issues, use reset, clearCache and revert operations.
Step 17: Example on Notes Table
This section provides examples of Notes table for various databases.
MySQL
CREATE TABLE Notes
(
`_id` INT(11) PRIMARY KEY NOT NULL AUTO_INCREMENT,
content VARCHAR(100),
deleted TINYINT(1),
`_updatedAt` TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL
);
CREATE UNIQUE INDEX Notes__id_uindex ON Notes (`_id`);
Postgres
CREATE TABLE "Notes"
(
_id SERIAL PRIMARY KEY NOT NULL,
content VARCHAR(100),
deleted BOOLEAN,
"_updatedAt" TIMESTAMP DEFAULT now() NOT NULL
);
CREATE UNIQUE INDEX "notes__id_uindex" ON "Notes" (_id);
CREATE OR REPLACE FUNCTION update_updatedAt_column()
RETURNS TRIGGER AS $$
BEGIN
NEW."_updatedAt" = now();
RETURN NEW;
END;
$$ language 'plpgsql';
CREATE TRIGGER update_Notes_updatedAt BEFORE UPDATE
ON "Notes" FOR EACH ROW EXECUTE PROCEDURE
update_updatedAt_column();
MSSQL
CREATE TABLE Notes
(
_id INT PRIMARY KEY NOT NULL IDENTITY,
content VARCHAR(100),
deleted BIT,
_updatedAt DATETIME DEFAULT getdate()
);
CREATE UNIQUE INDEX Notes__id_uindex ON Notes (_id);
CREATE TRIGGER update_Notes_updatedAt ON Notes
AFTER UPDATE
AS
UPDATE f set _updatedAt=GETDATE()
FROM
Notes AS f
INNER JOIN inserted
AS i
ON f._id = i._id;
Oracle
CREATE TABLE "Notes"
(
"_id" NUMBER(*) PRIMARY KEY NOT NULL,
"content" VARCHAR2(100),
"deleted" NUMBER(*) DEFAULT 0,
"_updatedAt" TIMESTAMP(6)
);
CREATE SEQUENCE notes_id_seq START WITH 1;
CREATE OR REPLACE TRIGGER notes_bir
BEFORE INSERT ON "Notes"
FOR EACH ROW
BEGIN
SELECT notes_id_seq.NEXTVAL
INTO :new."_id"
FROM dual;
END;
CREATE OR REPLACE TRIGGER update_Notes_updatedAt
BEFORE INSERT OR UPDATE ON "Notes"
FOR EACH ROW
BEGIN
:new."_updatedAt" := SYSTIMESTAMP;
END;