You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: general/app/development/development-guide.md
+46-5Lines changed: 46 additions & 5 deletions
Original file line number
Diff line number
Diff line change
@@ -284,15 +284,29 @@ Learn more about unit tests in the [Testing](#testing) section.
284
284
285
285
## Routing
286
286
287
-
All core features and addons can define their own routes, and we can do that in their main module. However, those are loaded when the application starts up, and that won't be desirable in most cases. In those situations, we can use [lazy loading](https://angular.io/guide/lazy-loading-ngmodules) to defer it until necessary. To encapsulate lazy functionality, we can define a [Routed Module](https://angular.io/guide/module-types#routed) named `{feature-name}LazyModule`. For example, the *login* core feature defines both a `CoreLoginModule` and a `CoreLoginLazyModule`.
287
+
All core features and addons can define their own routes, and we can do that in their main module. However, those are loaded when the application starts up, and that won't be desirable in most cases. We can use [lazy loading](https://angular.io/guide/lazy-loading-ngmodules) to defer loading routes until they are necessary. To encapsulate lazy functionality, we can define a [Routed Module](https://angular.io/guide/module-types#routed) named `{feature-name}LazyModule`. For example, the *login* core feature defines both a `CoreLoginModule` (for routes that are loaded when the application starts up) and a `CoreLoginLazyModule` (for routes that are loaded only when necessary).
288
+
289
+
### Dynamic Routes
288
290
289
291
With the [folders structure](#folders-structure) we're using, it is often the case where different core features or addons need to define routes depending on each other. For example, the *mainmenu* feature defines the layout and routes for the tabs that are always present at the bottom of the UI. But the home tab is defined in the *home* feature. In this scenario, it would be possible to just import the pages from the *home* module within the *mainmenu*, since both are core features and are allowed to know each other. But that approach can become messy, and what happens if an addon also needs to define a tab (like *privatefiles*)?
290
292
291
-
As described in the [addons/ folder documentation](#addons), the answer to this situation is using the dependency inversion pattern. Instead of the *mainmenu* depending on anything rendering a tab (*home*, *privatefiles*, etc.), we can make those depend on *mainmenu*. And we can do that using Angular's container.
293
+
As described in the [addons/ folder documentation](#addons), the answer to this situation is using the dependency inversion principle. Instead of the *mainmenu* depending on anything rendering a tab (*home*, *privatefiles*, etc.), we can make those depend on *mainmenu*. And we can do that using Angular's container.
294
+
295
+
In order to allow injecting routes from other modules, we create a separated [Routing Module](https://angular.io/guide/module-types#routing-ngmodules). This is the only situation where we'll have a dedicated module for routing. Any routes that are not meant to be injected can be defined directly on their main or lazy module.
296
+
297
+
It is often the case that modules using injected routes use a [RouterOutlet](https://angular.io/api/router/RouterOutlet). For that reason, injected routes can be defined either as children or siblings of the main route. The difference between those is that a child will be rendered within the outlet, whilst a sibling will replace the entire page. In order to make this distinction, routing modules accept either an array of routes to use as siblings or an object indicating both types of routes.
298
+
299
+
Finally, since these routes are defined dynamically, they cannot be imported statically when defining parent routes. They will need to be encapsulated on a builder function, taking an `injector` as an argument to resolve all the injected routes. You can see an example of this in the `buildTabMainRoutes`, and how it's used across the app.
300
+
301
+
### Split View Routes
292
302
293
-
In order to allow injecting routes from other modules, we create a separated [Routing Module](https://angular.io/guide/module-types#routing-ngmodules). This is the only situation where we'll have a dedicated module for routing, in order to reduce the amount of module files in a feature root folder. Any routes that are not injected can be defined directly on their main or lazy module.
303
+
Some pages in the app use a split-view pattern that consists of a navigation menu on the left, and the main content in the right (in LTR interfaces). It is typically used to display a list of items in the menu, and display the contents of the selected item in the content. For example, showing a list of settings on the left with their content on the right.
294
304
295
-
It is often the case that modules using injected routes have a [RouterOutlet](https://angular.io/api/router/RouterOutlet). For that reason, injected routes can be defined either as children or siblings of the main route. The difference between those is that a child will be rendered within the outlet, whilst a sibling will replace the entire page. In order to make this distinction, routing modules accept either an array of routes to use as siblings or an object indicating both types of routes.
305
+
This pattern is used in large screens (such as tablets), and logically is made up of two pages: one used for the menu and one for the content. The one with the menu defines an outlet for the content page, and in smaller devices (such as mobile phones), the outlet is hidden and navigating to items will override the entire page instead of populating the outlet. This is achieved by the styles and markup of the `<core-split-view>` component.
306
+
307
+
In order for the different behaviour to take place, routes are defined twice. Once where the content is a children of the menu, and again where the content is a sibling of the menu. These two definitions would clash in normal situations, but they are defined with a conditional that toggles them depending on the active breakpoint. You can find an example looking at the route definitions in the `CoreSettingsLazyModule`, which correspond with the routes that you can visit from the Main Menu > More > App Settings.
308
+
309
+
The navigation between these routes is often encapsulated within a `CoreListItemsManager` instance, that takes care of discerning the current active item and updating the route when selected items change. This manager will obtain the items from a `CoreRoutedItemsManagerSource`, which is necessary to enable [swipe navigation](#navigating-using-swipe-gestures).
296
310
297
311
### Navigating between routes
298
312
@@ -310,6 +324,14 @@ Other than navigation, this service also contains some helpers that are not avai
310
324
311
325
Make sure to [check out the full api](https://github.com/moodlehq/moodleapp/blob/main/src/core/services/navigator.ts) to learn more about the `CoreNavigator` service.
312
326
327
+
### Navigating using swipe gestures
328
+
329
+
Most pages that use a split-view in tablets can be navigated using swipe gestures in mobile devices. The navigation is often encapsulated within a `CoreSwipeNavigationItemsManager` instance.
330
+
331
+
As mentioned in the [split-view section](#split-view-routes), the items used by the manager are obtained from a `CoreRoutedItemsManagerSource`. This source will be reused between menu and content pages in mobile as well, so that swipe navigation respects any filters that have been applied in the menu page. In order to make sure that the same instance is reused, instead of creating a new one, these can be instantiated using the `CoreRoutedItemsManagerSourcesTracker.getOrCreateSource()` method. It will reuse instances that are still active, and when passed to managers the references will be cleared up when the managers are destroyed.
332
+
333
+
You can find an example of this pattern in `CoreUserParticipantsPage`, where participants can be filtered and the swipe navigation will respect the filtered results.
334
+
313
335
## Singletons
314
336
315
337
The application relies heavily on the [Singleton design pattern](https://en.wikipedia.org/wiki/Singleton_pattern), and there are two types of singletons used throughout the application: Pure singletons and Service singletons.
@@ -382,6 +404,26 @@ All the nomenclature can be a bit confusing, so let's do a recap:
382
404
- Service Singleton: An instance of a Singleton Service.
383
405
- Singleton Proxy: An object that relays method calls to a Service Singleton instance.
384
406
407
+
## Database
408
+
409
+
Most of the persistent data in the application is stored in SQLite databases. In particular, there is one database for global app configuration, and one for each site. Reading and writing data is encapsulated in the `CoreDatabaseTable` class. Each table can be configured to use one of the following caching strategies:
410
+
411
+
- Eager Caching: When the table is initialised, it will query all the records and store them in memory. This improves performance for data that is read very often, because reads will happen in-memory without touching the database. But it shouldn't be used for tables with a lot of records, to reduce memory consumption.
412
+
- Lazy Caching: Lazy caching works similar to eager caching, but instead of querying all the records upfront it'll remember records after reading them for the first time. This strategy is more appropriate for tables that are read often but have too many records to cache completely in memory.
413
+
- No Caching: Finally, for tables that are written more often than they are read, it is possible to disable caching altogether.
414
+
415
+
Something else important to note is that not all these tables are instantiated when the application is initialized, so for example even though a table may have Eager loading; it could be itself initialized lazily.
416
+
417
+
### Schema migrations
418
+
419
+
Table schemas are declared using `CoreAppSchema`, `CoreSiteSchema`, and `SQLiteDBTableSchema` interfaces; and invoked using `CoreApp.createTablesFromSchema()` and `CoreSitesProvider.applySiteSchemas()`. In the case of site tables, these can be registered with the `CORE_SITE_SCHEMAS` injection token and they'll be invoked automatically when a new site is created.
420
+
421
+
In order to make some changes in existing schemas, it'll be necessary to change the `version` number and implement the `migrate` method to perform any operations necessary during the migration.
422
+
423
+
### Legacy
424
+
425
+
Ideally, all interactions with the database would go through a `CoreDatabaseTable` instance. However, there is still some code using the previous approach through the `SQLiteDB` class. This should be avoided for new code, and eventually migrated to use the new approach to take advantage of caching.
426
+
385
427
## Application Lifecycle
386
428
387
429
When the application is launched, the contents of [index.html](#indexhtml) are rendered on screen. This file is intentionally concise because all the flare is added by JavaScript, and the splash screen will be covering the application UI until it has fully started. If you are developing in the browser, this will be a blank screen for you given that the splash screen is not available on the web. We are not targeting browsers in production, so it's acceptable to have this behaviour during development.
@@ -420,7 +462,6 @@ You can learn more about this in the [Acceptance testing for the Moodle App](./t
Copy file name to clipboardExpand all lines: general/app/development/link-handling/app-links.md
+1-1Lines changed: 1 addition & 1 deletion
Original file line number
Diff line number
Diff line change
@@ -17,7 +17,7 @@ When a user presses a link in the Moodle app, the behaviour changes depending on
17
17
18
18
## Extending the list of supported URLs
19
19
20
-
The app has a defined list of supported URLs. If you have a plugin adapted to work in the app and you want to support links to your plugin you will need to create a Link Handler. For more information and examples about this, please see the [Link handlers](../plugins-development-guide/index.md#link-handlers) documentation.
20
+
The app has a defined list of supported URLs. If you have a plugin adapted to work in the app and you want to support links to your plugin you will need to create a Link Handler. For more information and examples about this, please see the [CoreContentLinksDelegate](../plugins-development-guide/api-reference.md#corecontentlinksdelegate) documentation.
Push notifications will open the app when clicked, and you can customize which page is open by default.
10
+
11
+
The easiest way to achieve it is to include a `contexturl` property in your notification. Notice that using this property, the url will also be displayed when you see the notification in the web. You can override this behaviour in the app using `appurl` within `customdata`:
12
+
13
+
```php
14
+
$notification->customdata = [
15
+
'appurl' => $myurl->out(),
16
+
];
17
+
```
18
+
19
+
The url will be handled using the default [link handling](./app-links.md) in the app. If you want to implement some custom behaviour when opening notifications, you can achieve it with a Site Plugin implementing a [CorePushNotificationsDelegate](../plugins-development-guide/api-reference.md#corepushnotificationsdelegate) handler.
You can also launch a normal authentication process (allowing the authentication popup) and capture the redirect to `moodlemobile://...` created by the `admin/tool/mobile/launch.php` script and then execute the following in the console:
0 commit comments