Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Support for deep links #3995

Merged
merged 17 commits into from
May 23, 2023
Merged

feat: Support for deep links #3995

merged 17 commits into from
May 23, 2023

Conversation

g123k
Copy link
Collaborator

@g123k g123k commented May 22, 2023

Hi everyone,

This PR allows the app to support deep links (e.g.: clicking on a link in a search result and being redirected to the appropriate screen). We have decided to only support two routes: a product and the homepage.

A product will be opened if:

  • The url has at least two parts
  • The barcode contains at least 8 digits

Some examples: product/123456789 or produit/3017620422003/nutella-ferrero

Implementation

We now use the Navigator 2 (or router) in the app, with the help of go_router.
If this is the first time the user opens the app, the link will be ignored and, in general, if the onboarding is not successfully achieved, all links are ignored.

The redirect method of GoRouter is called both for internal routes and external ones (= url), that's why internal routes are prefixed with an underscore.

Product loader

The ProductDetails screen requires it to be in the database. That's why a ProductLoader will load all the required stuff on the server and then show the details. Obviously, products not found, and network errors are handled.

External links

A deep link can be called with an unsupported path (e.g.: /contact). In that case, we will redirect to the browser, through the ExternalPage.

Migration from the Navigator

Instead of calling Navigator.of(context), we now have to use AppNavigator.of(context) which embeds our GoRouter implementation.

Notes

I have not migrated how the onboarding, tabs and preferences handle their navigation, as it's not mandatory.

How to test it?

On Android, you can use this ADB command:

adb shell 'am start -a android.intent.action.VIEW -c android.intent.category.BROWSABLE -d "https://al.openfoodfacts.org/"' org.openfoodfacts.scanner

@g123k g123k added 🥫 Product page 🔗 Deeplinking Ensure people browsing Open Food Facts on the mobile web have a bridge to the mobile apps labels May 22, 2023
@g123k g123k requested a review from a team as a code owner May 22, 2023 08:26
@g123k g123k self-assigned this May 22, 2023
@github-actions github-actions bot added Android dependencies 🍎 iOS iOS specific issues or PRs 🌐 l10n Product addition The easier it is to add a product and get Nutri-Score, Eco-Score, the happier the users. product scan carousel 🤳🥫 Scan We need to be able to scan on low-end, old devices, even with a bad camera, connexion… summary card labels May 22, 2023
@codecov-commenter
Copy link

codecov-commenter commented May 22, 2023

Codecov Report

Merging #3995 (1431606) into develop (76762b1) will decrease coverage by 0.20%.
The diff coverage is 0.38%.

@@             Coverage Diff             @@
##           develop    #3995      +/-   ##
===========================================
- Coverage    10.96%   10.77%   -0.20%     
===========================================
  Files          265      270       +5     
  Lines        13090    13322     +232     
===========================================
  Hits          1435     1435              
- Misses       11655    11887     +232     
Impacted Files Coverage Δ
...cards/product_cards/smooth_product_card_found.dart 0.00% <0.00%> (ø)
...s/product_cards/smooth_product_card_not_found.dart 0.00% <0.00%> (ø)
...kages/smooth_app/lib/helpers/analytics_helper.dart 0.00% <ø> (ø)
...ooth_app/lib/helpers/extension_on_text_helper.dart 40.90% <0.00%> (-12.04%) ⬇️
packages/smooth_app/lib/main.dart 13.11% <0.00%> (+0.23%) ⬆️
...s/smooth_app/lib/pages/inherited_data_manager.dart 0.00% <0.00%> (ø)
.../smooth_app/lib/pages/navigator/app_navigator.dart 0.00% <0.00%> (ø)
...ges/smooth_app/lib/pages/navigator/error_page.dart 0.00% <0.00%> (ø)
.../smooth_app/lib/pages/navigator/external_page.dart 0.00% <0.00%> (ø)
...p/lib/pages/preferences/user_preferences_page.dart 93.82% <ø> (ø)
... and 8 more

... and 4 files with indirect coverage changes

📣 We’re building smart automated test selection to slash your CI/CD build times. Learn more

@github-actions github-actions bot added the 📈 Analytics We use Sentry and Matomo, with an opt-in system label May 22, 2023
@g123k g123k linked an issue May 22, 2023 that may be closed by this pull request
17 tasks
Copy link
Contributor

@monsieurtanuki monsieurtanuki left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @g123k!
For the record I have no previous experience with deep-links.
Please have a look at my comments.
The most important thing is that you probably confused two pages: "add new product" page and "display product" page. I assume that "add new product" page shouldn't be dealt with here.

Comment on lines 61 to 68
AppNavigator.of(context).push(
AppRoutes.PRODUCT_CREATOR(barcode),
);
if (callback != null) {
await callback!();
}

// TODO(g123k): Find another way
// if (callback != null) {
// await callback!();
// }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you're making a mistake here (which explains your TODO): here we open the page where you edit a new product. We don't open the ("new") product page.
Therefore I think you should remove your changes on that file.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PRODUCT_CREATOR actually opens AddNewProductPage.
The only problem we have now is that the push method doesn't return any result.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PRODUCT_CREATOR actually opens AddNewProductPage. The only problem we have now is that the push method doesn't return any result.

Then perhaps the concept of PRODUCT_CREATOR is flawed, or at least we should put it on the shelf.
To me the use-case is a user browsing and clicking on a link that automagically opens the app. A link that opens a known product, I can understand. A link that opens the search, I can understand. A link that opens an edit page for an unknown barcode, given that we scan on the app and we search on the app, I cannot quite picture that.
That said, perhaps the callback (that we actually set only once in that case) is redundant with whatever we do at the end of the AddNewProductPage, to be investigated.

But in general, I think that we assumed a long time ago that the deep-links were rather "read/display" pages than "write/edit" pages, specifically because they don't return values. Maybe it's not relevant, after all.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand your point, but the routes in GoRouter, doesn't necessary mean they have to be opened via a deep link. In that case, PRODUCT_CREATOR is just a named route, as we can find in the Navigator v1.

I have handled the case where a product doesn't exist, as it may happen.
Let's say you receive a link by email and the sender has made a typo => we have to do something for that.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand your point, but the routes in GoRouter, doesn't necessary mean they have to be opened via a deep link. In that case, PRODUCT_CREATOR is just a named route, as we can find in the Navigator v1.

Ok, but in that case if we want to handle the callback that you commented/TODOed, perhaps we would be better off making this page a full screen dialog instead of a page.

I have handled the case where a product doesn't exist, as it may happen. Let's say you receive a link by email and the sender has made a typo => we have to do something for that.

In that case I would rather land on a "The product doesn't exist" page (with a "do you want to create it?" button).
If not we somehow ask users to create new products with wrong barcodes!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the first point, OK I will revert what(s done here
Mmm for the second point, I let @teolemon what's the best implementation to use.

Copy link
Member

@teolemon teolemon May 23, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@monsieutanuki @g123k Mmh.. I can see some potential abuse potential but also the ability for third party apps to delegate some of their duties to Open Food Facts when a product is missing (some are currently using a web link)
I'd be in favor of giving the product addition approach a try, and kill it if we get abuse

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I let the feature as is.

Are we able as of now to detect from where the Product Creation page was opened?
Or should I add an Analytics event?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't believe we are. We can open it from scan, search, and now deep links, right ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, exactly. Is-it useful to add it, or should I leave it as is?

),
),
? () => AppNavigator.of(context).push(
AppRoutes.PREFERENCES(PreferencePageType.FOOD),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just checking: is it possible for the user to say, from the browser, "Ok you can send me to the app but this time I prefer to stay on the website"?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately, no (both on Android and iOS)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That means we'd better check if we have all the major features on the app, compared to the website on smartphone.
In order to avoid user frustration.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're at feature parity, but some (hard to implement vs ROI) stuff is missing (like revision history).
Simple solution: on the product page, we could add an optional button to open the classic webpage in a webview.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're at feature parity, but some (hard to implement vs ROI) stuff is missing (like revision history). Simple solution: on the product page, we could add an optional button to open the classic webpage in a webview.

I guess a button at the end of the product page would do the trick. Assuming we can exit the app using a URL the app is supposed to catch ;)

Comment on lines +302 to +303
// TODO(g123k): Improve this with sealed classes (Dart 3 is required)
// ignore_for_file: non_constant_identifier_names
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like in dart the constants should rather be written in camelCase. Is that the same with sealed classes?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sealed classes are actually like regular classes.
This is just a temporary way to distinguish a simple getter from this.
I know this isn't perfect

Comment on lines 305 to 313
AppRoutes._();

// Home page (or walkthrough during the onboarding)
static const String HOME = _SmoothGoRouter.HOME_PAGE;

// Product details (a [Product] is mandatory in the extra)
static String PRODUCT(String barcode) =>
'/${_SmoothGoRouter.PRODUCT_DETAILS_PAGE}/$barcode';

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
AppRoutes._();
// Home page (or walkthrough during the onboarding)
static const String HOME = _SmoothGoRouter.HOME_PAGE;
// Product details (a [Product] is mandatory in the extra)
static String PRODUCT(String barcode) =>
'/${_SmoothGoRouter.PRODUCT_DETAILS_PAGE}/$barcode';
final String path;
// Home page (or walkthrough during the onboarding)
AppRoutes.home() : path = _SmoothGoRouter.HOME_PAGE;
// Product details (a [Product] is mandatory in the extra)
AppRoutes.product(String barcode) : path =
'/${_SmoothGoRouter.PRODUCT_DETAILS_PAGE}/$barcode';

Would look better that way, I think.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That would be better, but it means that each entity contains an attribute for nothing.
And what should we do if you we want no one, but two elements?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That would be better, but it means that each entity contains an attribute for nothing.

The attribute being the String that you return, right? I don't see it as a problem, I see it as OOP.

And what should we do if you we want no one, but two elements?

Then with my OOP solution we can do it, like AppRoutes.product(String barcode, Color color).

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In your second example, you would still have only the path and merge the two data in the construct you mean?

packages/smooth_app/lib/pages/navigator/app_navigator.dart Outdated Show resolved Hide resolved
@teolemon
Copy link
Member

@monsieurtanuki @g123k

  • I'd like this merged and deployed quickly, at least on Android.
  • There's no path dependancy (we can iterate on the feature in subsequent releases without regrets on corner cutting done now)
  • There's a monthly 2.7 million visitors conversion opportunity from the website

@monsieurtanuki
Copy link
Contributor

I'd like this merged and deployed quickly, at least on Android.

@teolemon If you mean for a prod release, I guess we should list the other issues we would like to be fixed beforehand.
For a beta release, I have no strong objection against the current PR, and I've already listed my remarks.
In both cases, for the record I'll have limited time for Smoothie in the next 10 days. And after it's Roland Garros ;)

@teolemon
Copy link
Member

ok, @monsieurtanuki @g123k let's merge as is ?

@teolemon teolemon merged commit 4c44b06 into openfoodfacts:develop May 23, 2023
@g123k g123k deleted the deep_links branch May 23, 2023 14:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
📈 Analytics We use Sentry and Matomo, with an opt-in system Android 🔗 Deeplinking Ensure people browsing Open Food Facts on the mobile web have a bridge to the mobile apps dependencies 🍎 iOS iOS specific issues or PRs 🌐 l10n Product addition The easier it is to add a product and get Nutri-Score, Eco-Score, the happier the users. 🥫 Product page product scan carousel 🤳🥫 Scan We need to be able to scan on low-end, old devices, even with a bad camera, connexion… summary card
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Handle openfoodfacts.org links (Add deeplinking towards the product page)
4 participants