Skip to content

Commit

Permalink
Merge pull request #1070 from moodlemobile/app-docs
Browse files Browse the repository at this point in the history
App 4.4 documentation update
  • Loading branch information
andrewnicols committed Jul 2, 2024
2 parents bb1eba9 + 9a398d9 commit 161f1d7
Show file tree
Hide file tree
Showing 40 changed files with 2,770 additions and 2,211 deletions.
Binary file not shown.
Binary file not shown.
Binary file added general/app/_files/dynamic_templates.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added general/app/_files/static_templates.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
51 changes: 46 additions & 5 deletions general/app/development/development-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -284,15 +284,29 @@ Learn more about unit tests in the [Testing](#testing) section.

## Routing

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`.
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).

### Dynamic Routes

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*)?

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.
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.

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.

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.

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.

### Split View Routes

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.
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.

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.
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.

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.

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).

### Navigating between routes

Expand All @@ -310,6 +324,14 @@ Other than navigation, this service also contains some helpers that are not avai

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.

### Navigating using swipe gestures

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.

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.

You can find an example of this pattern in `CoreUserParticipantsPage`, where participants can be filtered and the swipe navigation will respect the filtered results.

## Singletons

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.
Expand Down Expand Up @@ -382,6 +404,26 @@ All the nomenclature can be a bit confusing, so let's do a recap:
- Service Singleton: An instance of a Singleton Service.
- Singleton Proxy: An object that relays method calls to a Service Singleton instance.

## Database

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:

- 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.
- 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.
- No Caching: Finally, for tables that are written more often than they are read, it is possible to disable caching altogether.

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.

### Schema migrations

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.

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.

### Legacy

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.

## Application Lifecycle

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.
Expand Down Expand Up @@ -420,7 +462,6 @@ You can learn more about this in the [Acceptance testing for the Moodle App](./t

## See also

- [Moodle App Scripts: gulp push](./scripts/gulp-push.md)
- [Moodle App Accessibility](./accessibility.md)
- [Moodle App Link Handling](./link-handling/app-links.md)
- [Moodle App Translation](https://docs.moodle.org/en/Moodle_App_Translation)
2 changes: 1 addition & 1 deletion general/app/development/link-handling/app-links.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ When a user presses a link in the Moodle app, the behaviour changes depending on

## Extending the list of supported URLs

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.
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.

## Opening links in an embedded browser

Expand Down
12 changes: 1 addition & 11 deletions general/app/development/link-handling/deep-linking.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
title: Moodle App Deep Linking
sidebar_label: Deep Linking
sidebar_label: Deep linking
sidebar_position: 2
tags:
- Moodle App
Expand Down Expand Up @@ -78,16 +78,6 @@ The redirect parameter can be a relative URL based on the base URL. The example
moodlemobile://https://domain.com?redirect=/course/view.php?id=2
```

## Before 3.7

Deep linking was introduced in version 3.6.1, but it had a different format that was updated in 3.7.0 to the one we use today.

This is an example of the previous format:

```text
moodlemobile://link=https://mysite.es/mod/choice/view.php?id=8
```

## See also

- [Custom URL Scheme Cordova plugin used by the app](https://github.com/EddyVerbruggen/Custom-URL-scheme).
19 changes: 19 additions & 0 deletions general/app/development/link-handling/notification-links.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
title: Moodle App Notification Links
sidebar_label: Notification links
sidebar_position: 3
tags:
- Moodle App
---

Push notifications will open the app when clicked, and you can customize which page is open by default.

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`:

```php
$notification->customdata = [
'appurl' => $myurl->out(),
];
```

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.
4 changes: 2 additions & 2 deletions general/app/development/network-debug.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,12 +72,12 @@ Here's how to debug web service errors:

You can execute the following in the JavaScript console:

```javascript
```js
window.handleOpenURL("moodlemobile://URL?token=WSTOKEN");
```

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:

```javascript
```js
window.handleOpenURL("moodlemobile://token=ABCxNGUxMD........=");
```
Loading

0 comments on commit 161f1d7

Please sign in to comment.