From 0020880c6a0196be19e61cc6753b9dd99b43bab0 Mon Sep 17 00:00:00 2001 From: Albert Gasset Date: Thu, 21 Jun 2018 15:59:06 +0200 Subject: [PATCH] MOBILE-2431: Wrap Ionic Native callbacks with NgZone.run --- src/addon/messages/messages.module.ts | 9 ++++--- src/addon/mod/chat/pages/chat/chat.ts | 9 ++++--- src/addon/mod/chat/pages/users/users.ts | 9 ++++--- src/addon/mod/feedback/pages/form/form.ts | 9 ++++--- .../mod/forum/pages/discussion/discussion.ts | 8 ++++-- .../providers/pushnotifications.ts | 25 +++++++++++++------ src/app/app.component.ts | 23 +++++++++-------- .../course/classes/main-activity-component.ts | 8 ++++-- .../pages/course-preview/course-preview.ts | 15 ++++++++--- src/providers/app.ts | 19 ++++++++------ src/providers/cron.ts | 9 ++++--- src/providers/filepool.ts | 10 +++++--- 12 files changed, 102 insertions(+), 51 deletions(-) diff --git a/src/addon/messages/messages.module.ts b/src/addon/messages/messages.module.ts index 90f4abff9..7eab2f334 100644 --- a/src/addon/messages/messages.module.ts +++ b/src/addon/messages/messages.module.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { NgModule } from '@angular/core'; +import { NgModule, NgZone } from '@angular/core'; import { Network } from '@ionic-native/network'; import { AddonMessagesProvider } from './providers/messages'; import { AddonMessagesOfflineProvider } from './providers/messages-offline'; @@ -69,7 +69,7 @@ export class AddonMessagesModule { contentLinksDelegate: CoreContentLinksDelegate, indexLinkHandler: AddonMessagesIndexLinkHandler, discussionLinkHandler: AddonMessagesDiscussionLinkHandler, sendMessageHandler: AddonMessagesSendMessageUserHandler, userDelegate: CoreUserDelegate, cronDelegate: CoreCronDelegate, syncHandler: AddonMessagesSyncCronHandler, - network: Network, messagesSync: AddonMessagesSyncProvider, appProvider: CoreAppProvider, + network: Network, zone: NgZone, messagesSync: AddonMessagesSyncProvider, appProvider: CoreAppProvider, localNotifications: CoreLocalNotificationsProvider, messagesProvider: AddonMessagesProvider, sitesProvider: CoreSitesProvider, linkHelper: CoreContentLinksHelperProvider, updateManager: CoreUpdateManagerProvider, settingsHandler: AddonMessagesSettingsHandler, settingsDelegate: CoreSettingsDelegate, @@ -88,7 +88,10 @@ export class AddonMessagesModule { // Sync some discussions when device goes online. network.onConnect().subscribe(() => { - messagesSync.syncAllDiscussions(undefined, true); + // Execute the callback in the Angular zone, so change detection doesn't stop working. + zone.run(() => { + messagesSync.syncAllDiscussions(undefined, true); + }); }); const notificationClicked = (notification: any): void => { diff --git a/src/addon/mod/chat/pages/chat/chat.ts b/src/addon/mod/chat/pages/chat/chat.ts index 082509e21..d724549c2 100644 --- a/src/addon/mod/chat/pages/chat/chat.ts +++ b/src/addon/mod/chat/pages/chat/chat.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { Component, ViewChild } from '@angular/core'; +import { Component, ViewChild, NgZone } from '@angular/core'; import { Content, IonicPage, ModalController, NavController, NavParams } from 'ionic-angular'; import { CoreAppProvider } from '@providers/app'; import { CoreLoggerProvider } from '@providers/logger'; @@ -52,7 +52,7 @@ export class AddonModChatChatPage { protected viewDestroyed = false; protected pollingRunning = false; - constructor(navParams: NavParams, logger: CoreLoggerProvider, network: Network, private navCtrl: NavController, + constructor(navParams: NavParams, logger: CoreLoggerProvider, network: Network, zone: NgZone, private navCtrl: NavController, private chatProvider: AddonModChatProvider, private appProvider: CoreAppProvider, sitesProvider: CoreSitesProvider, private modalCtrl: ModalController, private domUtils: CoreDomUtilsProvider, private textUtils: CoreTextUtilsProvider) { @@ -63,7 +63,10 @@ export class AddonModChatChatPage { this.currentUserBeep = 'beep ' + sitesProvider.getCurrentSiteUserId(); this.isOnline = this.appProvider.isOnline(); this.onlineObserver = network.onchange().subscribe((online) => { - this.isOnline = this.appProvider.isOnline(); + // Execute the callback in the Angular zone, so change detection doesn't stop working. + zone.run(() => { + this.isOnline = this.appProvider.isOnline(); + }); }); } diff --git a/src/addon/mod/chat/pages/users/users.ts b/src/addon/mod/chat/pages/users/users.ts index ee6a4f126..a9f4f175a 100644 --- a/src/addon/mod/chat/pages/users/users.ts +++ b/src/addon/mod/chat/pages/users/users.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { Component } from '@angular/core'; +import { Component, NgZone } from '@angular/core'; import { IonicPage, NavParams, ViewController } from 'ionic-angular'; import { CoreAppProvider } from '@providers/app'; import { CoreSitesProvider } from '@providers/sites'; @@ -38,14 +38,17 @@ export class AddonModChatUsersPage { protected sessionId: number; protected onlineObserver: any; - constructor(navParams: NavParams, network: Network, private appProvider: CoreAppProvider, + constructor(navParams: NavParams, network: Network, zone: NgZone, private appProvider: CoreAppProvider, private sitesProvider: CoreSitesProvider, private viewCtrl: ViewController, private domUtils: CoreDomUtilsProvider, private chatProvider: AddonModChatProvider) { this.sessionId = navParams.get('sessionId'); this.isOnline = this.appProvider.isOnline(); this.currentUserId = this.sitesProvider.getCurrentSiteUserId(); this.onlineObserver = network.onchange().subscribe((online) => { - this.isOnline = this.appProvider.isOnline(); + // Execute the callback in the Angular zone, so change detection doesn't stop working. + zone.run(() => { + this.isOnline = this.appProvider.isOnline(); + }); }); } diff --git a/src/addon/mod/feedback/pages/form/form.ts b/src/addon/mod/feedback/pages/form/form.ts index a1641c98c..fc1677027 100644 --- a/src/addon/mod/feedback/pages/form/form.ts +++ b/src/addon/mod/feedback/pages/form/form.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { Component, OnDestroy, Optional } from '@angular/core'; +import { Component, OnDestroy, Optional, NgZone } from '@angular/core'; import { IonicPage, NavParams, NavController, Content } from 'ionic-angular'; import { Network } from '@ionic-native/network'; import { TranslateService } from '@ngx-translate/core'; @@ -69,7 +69,7 @@ export class AddonModFeedbackFormPage implements OnDestroy { protected eventsProvider: CoreEventsProvider, protected feedbackSync: AddonModFeedbackSyncProvider, network: Network, protected translate: TranslateService, protected loginHelper: CoreLoginHelperProvider, protected linkHelper: CoreContentLinksHelperProvider, sitesProvider: CoreSitesProvider, - @Optional() private content: Content) { + @Optional() private content: Content, zone: NgZone) { this.module = navParams.get('module'); this.courseId = navParams.get('courseId'); @@ -82,7 +82,10 @@ export class AddonModFeedbackFormPage implements OnDestroy { // Refresh online status when changes. this.onlineObserver = network.onchange().subscribe((online) => { - this.offline = !online; + // Execute the callback in the Angular zone, so change detection doesn't stop working. + zone.run(() => { + this.offline = !online; + }); }); } diff --git a/src/addon/mod/forum/pages/discussion/discussion.ts b/src/addon/mod/forum/pages/discussion/discussion.ts index 94d36121b..752e73f4b 100644 --- a/src/addon/mod/forum/pages/discussion/discussion.ts +++ b/src/addon/mod/forum/pages/discussion/discussion.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { Component, Optional, OnDestroy, ViewChild } from '@angular/core'; +import { Component, Optional, OnDestroy, ViewChild, NgZone } from '@angular/core'; import { IonicPage, NavParams, Content } from 'ionic-angular'; import { Network } from '@ionic-native/network'; import { TranslateService } from '@ngx-translate/core'; @@ -78,6 +78,7 @@ export class AddonModForumDiscussionPage implements OnDestroy { constructor(navParams: NavParams, network: Network, + zone: NgZone, private appProvider: CoreAppProvider, private eventsProvider: CoreEventsProvider, private sitesProvider: CoreSitesProvider, @@ -99,7 +100,10 @@ export class AddonModForumDiscussionPage implements OnDestroy { this.isOnline = this.appProvider.isOnline(); this.onlineObserver = network.onchange().subscribe((online) => { - this.isOnline = this.appProvider.isOnline(); + // Execute the callback in the Angular zone, so change detection doesn't stop working. + zone.run(() => { + this.isOnline = this.appProvider.isOnline(); + }); }); this.isSplitViewOn = this.svComponent && this.svComponent.isOn(); diff --git a/src/addon/pushnotifications/providers/pushnotifications.ts b/src/addon/pushnotifications/providers/pushnotifications.ts index 444a7b1c9..971e8da42 100644 --- a/src/addon/pushnotifications/providers/pushnotifications.ts +++ b/src/addon/pushnotifications/providers/pushnotifications.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { Injectable } from '@angular/core'; +import { Injectable, NgZone } from '@angular/core'; import { Platform } from 'ionic-angular'; import { Badge } from '@ionic-native/badge'; import { Push, PushObject, PushOptions } from '@ionic-native/push'; @@ -65,7 +65,7 @@ export class AddonPushNotificationsProvider { protected pushNotificationsDelegate: AddonPushNotificationsDelegate, protected sitesProvider: CoreSitesProvider, private badge: Badge, private localNotificationsProvider: CoreLocalNotificationsProvider, private utils: CoreUtilsProvider, private textUtils: CoreTextUtilsProvider, private push: Push, - private configProvider: CoreConfigProvider, private device: Device) { + private configProvider: CoreConfigProvider, private device: Device, private zone: NgZone) { this.logger = logger.getInstance('AddonPushNotificationsProvider'); this.appDB = appProvider.getDB(); this.appDB.createTablesFromSchema(this.tablesSchema); @@ -326,19 +326,28 @@ export class AddonPushNotificationsProvider { const pushObject: PushObject = this.push.init(options); pushObject.on('notification').subscribe((notification: any) => { - this.logger.log('Received a notification', notification); - this.onMessageReceived(notification); + // Execute the callback in the Angular zone, so change detection doesn't stop working. + this.zone.run(() => { + this.logger.log('Received a notification', notification); + this.onMessageReceived(notification); + }); }); pushObject.on('registration').subscribe((data: any) => { - this.pushID = data.registrationId; - this.registerDeviceOnMoodle().catch((error) => { - this.logger.warn('Can\'t register device', error); + // Execute the callback in the Angular zone, so change detection doesn't stop working. + this.zone.run(() => { + this.pushID = data.registrationId; + this.registerDeviceOnMoodle().catch((error) => { + this.logger.warn('Can\'t register device', error); + }); }); }); pushObject.on('error').subscribe((error: any) => { - this.logger.warn('Error with Push plugin', error); + // Execute the callback in the Angular zone, so change detection doesn't stop working. + this.zone.run(() => { + this.logger.warn('Error with Push plugin', error); + }); }); }); } catch (ex) { diff --git a/src/app/app.component.ts b/src/app/app.component.ts index d93ed1754..8988711b5 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { Component, OnInit } from '@angular/core'; +import { Component, OnInit, NgZone } from '@angular/core'; import { Platform } from 'ionic-angular'; import { StatusBar } from '@ionic-native/status-bar'; import { CoreAppProvider } from '@providers/app'; @@ -33,7 +33,7 @@ export class MoodleMobileApp implements OnInit { protected lastUrls = {}; constructor(private platform: Platform, statusBar: StatusBar, logger: CoreLoggerProvider, - private eventsProvider: CoreEventsProvider, private loginHelper: CoreLoginHelperProvider, + private eventsProvider: CoreEventsProvider, private loginHelper: CoreLoginHelperProvider, private zone: NgZone, private appProvider: CoreAppProvider, private langProvider: CoreLangProvider, private sitesProvider: CoreSitesProvider) { this.logger = logger.getInstance('AppComponent'); @@ -101,17 +101,20 @@ export class MoodleMobileApp implements OnInit { // Handle app launched with a certain URL (custom URL scheme). ( window).handleOpenURL = (url: string): void => { - // First check that the URL hasn't been treated a few seconds ago. Sometimes this function is called more than once. - if (this.lastUrls[url] && Date.now() - this.lastUrls[url] < 3000) { - // Function called more than once, stop. - return; - } + // Execute the callback in the Angular zone, so change detection doesn't stop working. + this.zone.run(() => { + // First check that the URL hasn't been treated a few seconds ago. Sometimes this function is called more than once. + if (this.lastUrls[url] && Date.now() - this.lastUrls[url] < 3000) { + // Function called more than once, stop. + return; + } - this.logger.debug('App launched by URL ', url); + this.logger.debug('App launched by URL ', url); - this.lastUrls[url] = Date.now(); + this.lastUrls[url] = Date.now(); - this.eventsProvider.trigger(CoreEventsProvider.APP_LAUNCHED_URL, url); + this.eventsProvider.trigger(CoreEventsProvider.APP_LAUNCHED_URL, url); + }); }; // Listen for app launched URLs. If we receive one, check if it's a SSO authentication. diff --git a/src/core/course/classes/main-activity-component.ts b/src/core/course/classes/main-activity-component.ts index 909a9bbb6..28950d6bf 100644 --- a/src/core/course/classes/main-activity-component.ts +++ b/src/core/course/classes/main-activity-component.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { Injector, Input } from '@angular/core'; +import { Injector, Input, NgZone } from '@angular/core'; import { Content } from 'ionic-angular'; import { CoreSitesProvider } from '@providers/sites'; import { CoreCourseProvider } from '@core/course/providers/course'; @@ -60,10 +60,14 @@ export class CoreCourseModuleMainActivityComponent extends CoreCourseModuleMainR this.modulePrefetchDelegate = injector.get(CoreCourseModulePrefetchDelegate); const network = injector.get(Network); + const zone = injector.get(NgZone); // Refresh online status when changes. this.onlineObserver = network.onchange().subscribe((online) => { - this.isOnline = online; + // Execute the callback in the Angular zone, so change detection doesn't stop working. + zone.run(() => { + this.isOnline = online; + }); }); } diff --git a/src/core/courses/pages/course-preview/course-preview.ts b/src/core/courses/pages/course-preview/course-preview.ts index 7189d54a5..3d672fb12 100644 --- a/src/core/courses/pages/course-preview/course-preview.ts +++ b/src/core/courses/pages/course-preview/course-preview.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { Component, OnDestroy } from '@angular/core'; +import { Component, OnDestroy, NgZone } from '@angular/core'; import { IonicPage, NavController, NavParams, Platform, ModalController, Modal } from 'ionic-angular'; import { TranslateService } from '@ngx-translate/core'; import { CoreAppProvider } from '@providers/app'; @@ -69,7 +69,8 @@ export class CoreCoursesCoursePreviewPage implements OnDestroy { private coursesProvider: CoreCoursesProvider, private platform: Platform, private modalCtrl: ModalController, private translate: TranslateService, private eventsProvider: CoreEventsProvider, private courseOptionsDelegate: CoreCourseOptionsDelegate, private courseHelper: CoreCourseHelperProvider, - private courseProvider: CoreCourseProvider, private courseFormatDelegate: CoreCourseFormatDelegate) { + private courseProvider: CoreCourseProvider, private courseFormatDelegate: CoreCourseFormatDelegate, + private zone: NgZone) { this.course = navParams.get('course'); this.avoidOpenCourse = navParams.get('avoidOpenCourse'); @@ -292,9 +293,15 @@ export class CoreCoursesCoursePreviewPage implements OnDestroy { if (this.isDesktop || this.isMobile) { // Observe loaded pages in the InAppBrowser to check if the enrol process has ended. - inAppLoadSubscription = window.on('loadstart').subscribe(urlLoaded); + inAppLoadSubscription = window.on('loadstart').subscribe((event) => { + // Execute the callback in the Angular zone, so change detection doesn't stop working. + this.zone.run(() => urlLoaded(event)); + }); // Observe window closed. - inAppExitSubscription = window.on('exit').subscribe(inAppClosed); + inAppExitSubscription = window.on('exit').subscribe(() => { + // Execute the callback in the Angular zone, so change detection doesn't stop working. + this.zone.run(inAppClosed); + }); } if (this.isDesktop) { diff --git a/src/providers/app.ts b/src/providers/app.ts index 0a7a2d2e2..bac647c0a 100644 --- a/src/providers/app.ts +++ b/src/providers/app.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { Injectable } from '@angular/core'; +import { Injectable, NgZone } from '@angular/core'; import { Platform, App, NavController } from 'ionic-angular'; import { Keyboard } from '@ionic-native/keyboard'; import { Network } from '@ionic-native/network'; @@ -70,18 +70,23 @@ export class CoreAppProvider { protected isKeyboardShown = false; constructor(dbProvider: CoreDbProvider, private platform: Platform, private keyboard: Keyboard, private appCtrl: App, - private network: Network, logger: CoreLoggerProvider, events: CoreEventsProvider) { + private network: Network, logger: CoreLoggerProvider, events: CoreEventsProvider, zone: NgZone) { this.logger = logger.getInstance('CoreAppProvider'); this.db = dbProvider.getDB(this.DBNAME); this.keyboard.onKeyboardShow().subscribe((data) => { - this.isKeyboardShown = true; - events.trigger(CoreEventsProvider.KEYBOARD_CHANGE, this.isKeyboardShown); - + // Execute the callback in the Angular zone, so change detection doesn't stop working. + zone.run(() => { + this.isKeyboardShown = true; + events.trigger(CoreEventsProvider.KEYBOARD_CHANGE, this.isKeyboardShown); + }); }); this.keyboard.onKeyboardHide().subscribe((data) => { - this.isKeyboardShown = false; - events.trigger(CoreEventsProvider.KEYBOARD_CHANGE, this.isKeyboardShown); + // Execute the callback in the Angular zone, so change detection doesn't stop working. + zone.run(() => { + this.isKeyboardShown = false; + events.trigger(CoreEventsProvider.KEYBOARD_CHANGE, this.isKeyboardShown); + }); }); } diff --git a/src/providers/cron.ts b/src/providers/cron.ts index d891d44b2..d578fd1f8 100644 --- a/src/providers/cron.ts +++ b/src/providers/cron.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { Injectable } from '@angular/core'; +import { Injectable, NgZone } from '@angular/core'; import { Network } from '@ionic-native/network'; import { CoreAppProvider } from './app'; import { CoreConfigProvider } from './config'; @@ -115,7 +115,7 @@ export class CoreCronDelegate { protected queuePromise = Promise.resolve(); constructor(logger: CoreLoggerProvider, private appProvider: CoreAppProvider, private configProvider: CoreConfigProvider, - private utils: CoreUtilsProvider, network: Network) { + private utils: CoreUtilsProvider, network: Network, zone: NgZone) { this.logger = logger.getInstance('CoreCronDelegate'); this.appDB = this.appProvider.getDB(); @@ -123,7 +123,10 @@ export class CoreCronDelegate { // When the app is re-connected, start network handlers that were stopped. network.onConnect().subscribe(() => { - this.startNetworkHandlers(); + // Execute the callback in the Angular zone, so change detection doesn't stop working. + zone.run(() => { + this.startNetworkHandlers(); + }); }); } diff --git a/src/providers/filepool.ts b/src/providers/filepool.ts index 8a350fc0a..31c53a230 100644 --- a/src/providers/filepool.ts +++ b/src/providers/filepool.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { Injectable } from '@angular/core'; +import { Injectable, NgZone } from '@angular/core'; import { Network } from '@ionic-native/network'; import { CoreAppProvider } from './app'; import { CoreEventsProvider } from './events'; @@ -436,7 +436,8 @@ export class CoreFilepoolProvider { private sitesProvider: CoreSitesProvider, private wsProvider: CoreWSProvider, private textUtils: CoreTextUtilsProvider, private utils: CoreUtilsProvider, private mimeUtils: CoreMimetypeUtilsProvider, private urlUtils: CoreUrlUtilsProvider, private timeUtils: CoreTimeUtilsProvider, private eventsProvider: CoreEventsProvider, initDelegate: CoreInitDelegate, - network: Network, private pluginFileDelegate: CorePluginFileDelegate, private domUtils: CoreDomUtilsProvider) { + network: Network, private pluginFileDelegate: CorePluginFileDelegate, private domUtils: CoreDomUtilsProvider, + zone: NgZone) { this.logger = logger.getInstance('CoreFilepoolProvider'); this.appDB = this.appProvider.getDB(); @@ -450,7 +451,10 @@ export class CoreFilepoolProvider { // Start queue when device goes online. network.onConnect().subscribe(() => { - this.checkQueueProcessing(); + // Execute the callback in the Angular zone, so change detection doesn't stop working. + zone.run(() => { + this.checkQueueProcessing(); + }); }); }); }