Skip to content

Commit

Permalink
Merge pull request #660 from Bungeefan/uri_deep_link
Browse files Browse the repository at this point in the history
Correct handling of new URI API (fixes Deep Link Handling)
  • Loading branch information
slovnicki authored Mar 9, 2024
2 parents dcda31e + c70aca7 commit f113906
Show file tree
Hide file tree
Showing 6 changed files with 132 additions and 67 deletions.
9 changes: 1 addition & 8 deletions package/lib/src/beam_location.dart
Original file line number Diff line number Diff line change
Expand Up @@ -520,14 +520,7 @@ class RoutesBeamLocation extends BeamLocation<BeamState> {

final matched = <Pattern, String>{};
var overrideNotFound = false;
if (routeInformation.uri.toString() != '/' &&
(routeInformation.uri.toString().endsWith('/'))) {
final location = routeInformation.uri.toString();
routeInformation = routeInformation.copyWith(
location: location.substring(0, location.length - 1),
);
}
final uri = routeInformation.uri;
final uri = Utils.removeTrailingSlash(routeInformation.uri);

for (final route in routes) {
if (route is String) {
Expand Down
5 changes: 2 additions & 3 deletions package/lib/src/beam_page.dart
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import 'package:beamer/beamer.dart';
import 'package:beamer/src/utils.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';

import 'package:beamer/beamer.dart';

/// Types for how to route should be built.
///
/// See [BeamPage.type]
Expand Down Expand Up @@ -145,7 +144,7 @@ class BeamPage extends Page {

delegate.update(
configuration: delegate.configuration.copyWith(
location: popUri.toString(),
uri: popUri,
state: lastRouteInformation?.state,
),
data: data,
Expand Down
9 changes: 3 additions & 6 deletions package/lib/src/beam_state.dart
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import 'dart:convert';

import 'package:flutter/widgets.dart';

import 'package:beamer/src/utils.dart';
import 'package:beamer/src/beam_location.dart';
import 'package:beamer/src/utils.dart';
import 'package:flutter/widgets.dart';

/// A class to mix with when defining a custom state for [BeamLocation].
///
Expand Down Expand Up @@ -71,10 +70,8 @@ class BeamState with RouteInformationSerializable<BeamState> {
BeamLocation? beamLocation,
Object? routeState,
}) {
uriString = Utils.trimmed(uriString);
final uri = Uri.parse(uriString);
return BeamState.fromUri(
uri,
Utils.removeTrailingSlash(Uri.parse(uriString)),
beamLocation: beamLocation,
routeState: routeState,
);
Expand Down
12 changes: 7 additions & 5 deletions package/lib/src/beamer_delegate.dart
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ class BeamerDelegate extends RouterDelegate<RouteInformation>
/// This is not null only if multiple [Beamer]s are used;
/// `*App.router` and at least one more [Beamer] in the Widget tree.
BeamerDelegate? get parent => _parent;

set parent(BeamerDelegate? parent) {
if (parent == null && _parent != null) {
_parent!.removeListener(_updateFromParent);
Expand Down Expand Up @@ -802,8 +803,9 @@ class BeamerDelegate extends RouterDelegate<RouteInformation>
configuration = currentBeamLocation.state.routeInformation;
} else if (uri.path == '/') {
configuration = RouteInformation(
uri: Uri.parse(
initialPath + (uri.query.isNotEmpty ? '?${uri.query}' : ''),
uri: uri.replace(
path: initialPath,
query: uri.hasQuery ? uri.query : null,
),
);
}
Expand All @@ -813,8 +815,8 @@ class BeamerDelegate extends RouterDelegate<RouteInformation>

@override
SynchronousFuture<void> setNewRoutePath(RouteInformation configuration) {
if (configuration.uri.toString() == '/' && initialPath != '/') {
configuration = configuration.copyWith(location: initialPath);
if (configuration.uri.path == '/' && initialPath != '/') {
configuration = configuration.copyWith(uri: Uri.parse(initialPath));
}
update(configuration: configuration);
return SynchronousFuture(null);
Expand Down Expand Up @@ -907,7 +909,7 @@ class BeamerDelegate extends RouterDelegate<RouteInformation>
);
}

if (clearBeamingHistoryOn.contains(configuration.uri.toString())) {
if (clearBeamingHistoryOn.contains(configuration.uri.path)) {
_clearBeamingHistory();
}
}
Expand Down
60 changes: 31 additions & 29 deletions package/lib/src/utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -231,65 +231,67 @@ abstract class Utils {
}
}

/// Removes the trailing / in an URI String and returns the result.
/// Removes the trailing / in an URI path and returns the new URI.
///
/// If there is no trailing /, returns the input.
static String trimmed(String? uriString) {
if (uriString == null) {
return '';
/// If there is no trailing /, returns the input URI.
static Uri removeTrailingSlash(Uri uri) {
String path = uri.path;
if (path.length > 1 && path.endsWith('/')) {
return uri.replace(path: path.substring(0, path.length - 1));
}
if (uriString.length > 1 && uriString.endsWith('/')) {
return uriString.substring(0, uriString.length - 1);
return uri;
}

/// If [incoming] is a relative URI append it to [current].
/// Else, return incoming URI.
static Uri maybeAppend(Uri current, Uri incoming) {
if (!incoming.hasAbsolutePath && !incoming.hasEmptyPath) {
String currentPath =
current.path.endsWith('/') ? current.path : '${current.path}/';
return current.replace(
path: currentPath + incoming.path,
query: incoming.hasQuery ? incoming.query : null,
fragment: incoming.hasFragment ? incoming.fragment : null,
);
}
return uriString;
return incoming;
}

/// If incoming RouteInformation doesn't start with slash,
/// append it to the current RouteInformation.location.
/// Merges the URIs of the RouteInformation's.
///
/// Else, return incoming RouteInformation
static RouteInformation maybeAppend(
/// See [maybeAppend] and [removeTrailingSlash].
static RouteInformation mergeConfiguration(
RouteInformation current,
RouteInformation incoming,
) {
final incomingLocation = incoming.uri.toString();
if (!incomingLocation.startsWith('/')) {
return current.copyWith(
location: current.uri.toString().endsWith('/')
? '${current.uri}$incomingLocation'
: '${current.uri}/$incomingLocation',
state: incoming.state,
);
}
return incoming;
return incoming.copyWith(
uri: maybeAppend(current.uri, removeTrailingSlash(incoming.uri)),
);
}

/// Creates a new configuration for [BeamerDelegate.update].
///
/// Takes into consideration trimming and potentially appending
/// the incoming to current (used in relative beaming).
///
/// See [trimmed] and [maybeAppend].
/// See [removeTrailingSlash] and [maybeAppend].
static RouteInformation createNewConfiguration(
RouteInformation current,
RouteInformation incoming,
) {
incoming = incoming.copyWith(
location: trimmed(incoming.uri.toString()),
);
return maybeAppend(current, incoming);
return mergeConfiguration(current, incoming);
}
}

/// Some convenient extension methods on [RouteInformation].
extension BeamerRouteInformationExtension on RouteInformation {
/// Returns a new [RouteInformation] created from this.
RouteInformation copyWith({
String? location,
Uri? uri,
Object? state,
}) {
return RouteInformation(
uri: Uri.parse(location ?? this.uri.toString()),
uri: uri ?? this.uri,
state: state ?? this.state,
);
}
Expand Down
104 changes: 88 additions & 16 deletions package/test/utils_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -125,48 +125,120 @@ void main() {
});

group('Creating new configuration for BeamerDelegate', () {
test('Trimming', () {
expect(Utils.trimmed(null), '');
expect(Utils.trimmed('/'), '/');
expect(Utils.trimmed('/xxx/'), '/xxx');
test('Remove trailing slashes', () {
expect(Utils.removeTrailingSlash(Uri.parse('')), Uri.parse(''));
expect(Utils.removeTrailingSlash(Uri.parse('/')), Uri.parse('/'));
expect(Utils.removeTrailingSlash(Uri.parse('/xxx/')), Uri.parse('/xxx'));
expect(
Utils.removeTrailingSlash(Uri.parse('https://example.com/')),
Uri.parse('https://example.com/'),
);
expect(
Utils.removeTrailingSlash(Uri.parse('https://example.com/test/')),
Uri.parse('https://example.com/test'),
);
});

test('Appending without new routeState', () {
final current = RouteInformation(uri: Uri.parse('/current'));
test('Appending URIs to relative URI', () {
final current = Uri.parse('/current');
expect(
Utils.maybeAppend(current, RouteInformation(uri: Uri.parse('incoming')))
.uri
.toString(),
Utils.maybeAppend(current, Uri.parse('incoming')).toString(),
'/current/incoming',
);
expect(
Utils.maybeAppend(current, Uri.parse('/incoming')).toString(),
'/incoming',
);
expect(
Utils.maybeAppend(current, Uri.parse('')).toString(),
'',
);
expect(
Utils.maybeAppend(current, Uri.parse('/')).toString(),
'/',
);
expect(
Utils.maybeAppend(
current, RouteInformation(uri: Uri.parse('/incoming')))
.uri
.toString(),
current,
Uri.parse('example://app/incoming'),
).toString(),
'example://app/incoming',
);
expect(
Utils.maybeAppend(current, Uri.parse('//app/incoming')).toString(),
'//app/incoming',
);
});

test('Appending URIs to absolute URI', () {
final current = Uri.parse('example://app/current');
expect(
Utils.maybeAppend(current, Uri.parse('incoming')).toString(),
'example://app/current/incoming',
);
expect(
Utils.maybeAppend(current, Uri.parse('/incoming')).toString(),
'/incoming',
);
expect(
Utils.maybeAppend(current, Uri.parse('')).toString(),
'',
);
expect(
Utils.maybeAppend(current, Uri.parse('/')).toString(),
'/',
);
expect(
Utils.maybeAppend(
current,
Uri.parse('example://app/incoming'),
).toString(),
'example://app/incoming',
);
expect(
Utils.maybeAppend(current, Uri.parse('//app/incoming')).toString(),
'//app/incoming',
);
});

test('Appending with new routeState', () {
test('Merging with new routeState', () {
final current = RouteInformation(uri: Uri.parse('/current'));
expect(
Utils.maybeAppend(current, RouteInformation(uri: Uri(), state: 42))
Utils.mergeConfiguration(
current, RouteInformation(uri: Uri(), state: 42))
.state,
42,
);
expect(
Utils.maybeAppend(current,
Utils.mergeConfiguration(current,
RouteInformation(uri: Uri.parse('incoming'), state: 42))
.state,
42,
);
expect(
Utils.maybeAppend(current,
Utils.mergeConfiguration(current,
RouteInformation(uri: Uri.parse('/incoming'), state: 42))
.state,
42,
);
expect(
Utils.mergeConfiguration(
current,
RouteInformation(
uri: Uri.parse('example://app/incoming'),
state: 42,
)).state,
42,
);
expect(
Utils.mergeConfiguration(
current,
RouteInformation(
uri: Uri.parse('//app/incoming'),
state: 42,
)).state,
42,
);
});
});

Expand Down

0 comments on commit f113906

Please sign in to comment.