Skip to content

Commit be0b3dc

Browse files
Compass App: Add "server" dart shelf-app and "shared" dart package (#2359)
This PR introduces two new subprojects: - `compass_server` under `compass_app/server`. - `compass_model` under `compass_app/model`. **`compass_server`** - Dart server implemented using `shelf`. - Created with the `dart create -t server-shelf` template. - Implements two REST endpoints: - `GET /continent`: Returns the list of `Continent` as JSON. - `GET /destination`: Returns the list of `Destination` as JSON. - Generated Docker files have been removed. - Implemented tests. - TODO: Implement some basic auth. **`compass_model`** - Dart package to share data model classes between the `server` and `app`. - Contains the data model classes (`Continent`, `Destination`). - Generated JSON from/To methods and data classes using `freezed`. - The sole purpose of this package is to host the data model. Other shared code should go in a different package. **Other changes** - Created an API Client to connect to the local dart server. - Created "remote" repositories, which also implement a basic in-memory caching strategy. - Created two dependency configurations, one with local repositories and one with remote repos. - Created two application main targets to select configuration: - `lib/main_development.dart` which starts the app with the "local" data configuration. - `lib/main_staging.dart` which starts the app with the "remove" (local dart server) configuration. - `lib/main.dart` still works as default entry point. - Implemented tests for remote repositories. ## Pre-launch Checklist - [x] I read the [Flutter Style Guide] _recently_, and have followed its advice. - [x] I signed the [CLA]. - [x] I read the [Contributors Guide]. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-devrel channel on [Discord]. <!-- Links --> [Flutter Style Guide]: https://github.com/flutter/flutter/blob/master/docs/contributing/Style-guide-for-Flutter-repo.md [CLA]: https://cla.developers.google.com/ [Discord]: https://github.com/flutter/flutter/blob/master/docs/contributing/Chat.md [Contributors Guide]: https://github.com/flutter/samples/blob/main/CONTRIBUTING.md
1 parent 496b467 commit be0b3dc

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+2496
-118
lines changed

compass_app/app/lib/config/dependencies.dart

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,34 @@ import 'package:provider/provider.dart';
33

44
import '../data/repositories/continent/continent_repository.dart';
55
import '../data/repositories/continent/continent_repository_local.dart';
6+
import '../data/repositories/continent/continent_repository_remote.dart';
67
import '../data/repositories/destination/destination_repository.dart';
78
import '../data/repositories/destination/destination_repository_local.dart';
9+
import '../data/repositories/destination/destination_repository_remote.dart';
10+
import '../data/services/api_client.dart';
811

9-
/// Configure dependencies as a list of Providers
10-
List<SingleChildWidget> get providers {
11-
// List of Providers
12+
/// Configure dependencies for remote data.
13+
/// This dependency list uses repositories that connect to a remote server.
14+
List<SingleChildWidget> get providersRemote {
15+
final apiClient = ApiClient();
16+
17+
return [
18+
Provider.value(
19+
value: DestinationRepositoryRemote(
20+
apiClient: apiClient,
21+
) as DestinationRepository,
22+
),
23+
Provider.value(
24+
value: ContinentRepositoryRemote(
25+
apiClient: apiClient,
26+
) as ContinentRepository,
27+
),
28+
];
29+
}
30+
31+
/// Configure dependencies for local data.
32+
/// This dependency list uses repositories that provide local data.
33+
List<SingleChildWidget> get providersLocal {
1234
return [
1335
Provider.value(
1436
value: DestinationRepositoryLocal() as DestinationRepository,

compass_app/app/lib/data/models/continent.dart

Lines changed: 0 additions & 12 deletions
This file was deleted.

compass_app/app/lib/data/models/destination.dart

Lines changed: 0 additions & 50 deletions
This file was deleted.

compass_app/app/lib/data/repositories/continent/continent_repository.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1+
import 'package:compass_model/model.dart';
2+
13
import '../../../utils/result.dart';
2-
import '../../models/continent.dart';
34

45
/// Data source with all possible continents.
56
abstract class ContinentRepository {

compass_app/app/lib/data/repositories/continent/continent_repository_local.dart

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,40 @@
1+
import 'package:compass_model/model.dart';
2+
13
import '../../../utils/result.dart';
2-
import '../../models/continent.dart';
34
import 'continent_repository.dart';
45

5-
/// Local data source with all possible regions.
6+
/// Local data source with all possible continents.
67
class ContinentRepositoryLocal implements ContinentRepository {
78
@override
89
Future<Result<List<Continent>>> getContinents() {
910
return Future.value(
1011
Result.ok(
1112
[
12-
Continent(
13+
const Continent(
1314
name: 'Europe',
1415
imageUrl: 'https://rstr.in/google/tripedia/TmR12QdlVTT',
1516
),
16-
Continent(
17+
const Continent(
1718
name: 'Asia',
1819
imageUrl: 'https://rstr.in/google/tripedia/VJ8BXlQg8O1',
1920
),
20-
Continent(
21+
const Continent(
2122
name: 'South America',
2223
imageUrl: 'https://rstr.in/google/tripedia/flm_-o1aI8e',
2324
),
24-
Continent(
25+
const Continent(
2526
name: 'Africa',
2627
imageUrl: 'https://rstr.in/google/tripedia/-nzi8yFOBpF',
2728
),
28-
Continent(
29+
const Continent(
2930
name: 'North America',
3031
imageUrl: 'https://rstr.in/google/tripedia/jlbgFDrSUVE',
3132
),
32-
Continent(
33+
const Continent(
3334
name: 'Oceania',
3435
imageUrl: 'https://rstr.in/google/tripedia/vxyrDE-fZVL',
3536
),
36-
Continent(
37+
const Continent(
3738
name: 'Australia',
3839
imageUrl: 'https://rstr.in/google/tripedia/z6vy6HeRyvZ',
3940
),
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import 'package:compass_model/model.dart';
2+
3+
import '../../../utils/result.dart';
4+
import '../../services/api_client.dart';
5+
import 'continent_repository.dart';
6+
7+
/// Remote data source for [Continent].
8+
/// Implements basic local caching.
9+
/// See: https://docs.flutter.dev/get-started/fwe/local-caching
10+
class ContinentRepositoryRemote implements ContinentRepository {
11+
ContinentRepositoryRemote({
12+
required ApiClient apiClient,
13+
}) : _apiClient = apiClient;
14+
15+
final ApiClient _apiClient;
16+
17+
List<Continent>? _cachedData;
18+
19+
@override
20+
Future<Result<List<Continent>>> getContinents() async {
21+
if (_cachedData == null) {
22+
// No cached data, request continents
23+
final result = await _apiClient.getContinents();
24+
if (result is Ok) {
25+
// Store value if result Ok
26+
_cachedData = result.asOk.value;
27+
}
28+
return result;
29+
} else {
30+
// Return cached data if available
31+
return Result.ok(_cachedData!);
32+
}
33+
}
34+
}

compass_app/app/lib/data/repositories/destination/destination_repository.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1+
import 'package:compass_model/model.dart';
2+
13
import '../../../utils/result.dart';
2-
import '../../models/destination.dart';
34

45
/// Data source with all possible destinations
56
abstract class DestinationRepository {

compass_app/app/lib/data/repositories/destination/destination_repository_local.dart

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import 'dart:convert';
22

3+
import 'package:compass_model/model.dart';
4+
import 'package:flutter/services.dart' show rootBundle;
5+
36
import '../../../utils/result.dart';
4-
import '../../models/destination.dart';
57
import 'destination_repository.dart';
68

7-
import 'package:flutter/services.dart' show rootBundle;
8-
99
/// Local implementation of DestinationRepository
1010
/// Uses data from assets folder
1111
class DestinationRepositoryLocal implements DestinationRepository {
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import 'package:compass_model/model.dart';
2+
3+
import '../../../utils/result.dart';
4+
import '../../services/api_client.dart';
5+
import 'destination_repository.dart';
6+
7+
/// Remote data source for [Destination].
8+
/// Implements basic local caching.
9+
/// See: https://docs.flutter.dev/get-started/fwe/local-caching
10+
class DestinationRepositoryRemote implements DestinationRepository {
11+
DestinationRepositoryRemote({
12+
required ApiClient apiClient,
13+
}) : _apiClient = apiClient;
14+
15+
final ApiClient _apiClient;
16+
17+
List<Destination>? _cachedData;
18+
19+
@override
20+
Future<Result<List<Destination>>> getDestinations() async {
21+
if (_cachedData == null) {
22+
// No cached data, request destinations
23+
final result = await _apiClient.getDestinations();
24+
if (result is Ok) {
25+
// Store value if result Ok
26+
_cachedData = result.asOk.value;
27+
}
28+
return result;
29+
} else {
30+
// Return cached data if available
31+
return Result.ok(_cachedData!);
32+
}
33+
}
34+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import 'dart:convert';
2+
import 'dart:io';
3+
import 'package:compass_model/model.dart';
4+
5+
import '../../utils/result.dart';
6+
7+
// TODO: Basic auth request
8+
// TODO: Configurable baseurl/host/port
9+
class ApiClient {
10+
Future<Result<List<Continent>>> getContinents() async {
11+
final client = HttpClient();
12+
try {
13+
final request = await client.get('localhost', 8080, '/continent');
14+
final response = await request.close();
15+
if (response.statusCode == 200) {
16+
final stringData = await response.transform(utf8.decoder).join();
17+
final json = jsonDecode(stringData) as List<dynamic>;
18+
return Result.ok(
19+
json.map((element) => Continent.fromJson(element)).toList());
20+
} else {
21+
return Result.error(const HttpException("Invalid response"));
22+
}
23+
} on Exception catch (error) {
24+
return Result.error(error);
25+
} finally {
26+
client.close();
27+
}
28+
}
29+
30+
Future<Result<List<Destination>>> getDestinations() async {
31+
final client = HttpClient();
32+
try {
33+
final request = await client.get('localhost', 8080, '/destination');
34+
final response = await request.close();
35+
if (response.statusCode == 200) {
36+
final stringData = await response.transform(utf8.decoder).join();
37+
final json = jsonDecode(stringData) as List<dynamic>;
38+
return Result.ok(
39+
json.map((element) => Destination.fromJson(element)).toList());
40+
} else {
41+
return Result.error(const HttpException("Invalid response"));
42+
}
43+
} on Exception catch (error) {
44+
return Result.error(error);
45+
} finally {
46+
client.close();
47+
}
48+
}
49+
}

compass_app/app/lib/main.dart

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,14 @@
1-
import 'config/dependencies.dart';
21
import 'ui/core/themes/theme.dart';
32
import 'routing/router.dart';
43
import 'package:flutter/material.dart';
5-
import 'package:provider/provider.dart';
64

75
import 'ui/core/ui/scroll_behavior.dart';
6+
import 'main_development.dart' as development;
87

8+
/// Default main method
99
void main() {
10-
runApp(
11-
MultiProvider(
12-
// Loading the default providers
13-
// NOTE: We can load different configurations e.g. fakes
14-
providers: providers,
15-
child: const MainApp(),
16-
),
17-
);
10+
// Launch development config by default
11+
development.main();
1812
}
1913

2014
class MainApp extends StatelessWidget {
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import 'package:flutter/material.dart';
2+
import 'package:provider/provider.dart';
3+
4+
import 'config/dependencies.dart';
5+
import 'main.dart';
6+
7+
/// Development config entry point.
8+
/// Launch with `flutter run --target lib/main_development.dart`.
9+
/// Uses local data.
10+
void main() {
11+
runApp(
12+
MultiProvider(
13+
providers: providersLocal,
14+
child: const MainApp(),
15+
),
16+
);
17+
}

compass_app/app/lib/main_staging.dart

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import 'package:flutter/material.dart';
2+
import 'package:provider/provider.dart';
3+
4+
import 'config/dependencies.dart';
5+
import 'main.dart';
6+
7+
/// Staging config entry point.
8+
/// Launch with `flutter run --target lib/main_staging.dart`.
9+
/// Uses remote data from a server.
10+
void main() {
11+
runApp(
12+
MultiProvider(
13+
providers: providersRemote,
14+
child: const MainApp(),
15+
),
16+
);
17+
}

compass_app/app/lib/routing/router.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ final router = GoRouter(
1313
GoRoute(
1414
path: '/',
1515
builder: (context, state) {
16-
final viewModel = SearchFormViewModel(continentRepository: context.read());
16+
final viewModel =
17+
SearchFormViewModel(continentRepository: context.read());
1718
return SearchFormScreen(viewModel: viewModel);
1819
},
1920
routes: [

compass_app/app/lib/ui/core/themes/colors.dart

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,7 @@ class AppColors {
88
static const grey3 = Color(0xFFA4A4A4);
99
static const whiteTransparent =
1010
Color(0x4DFFFFFF); // Figma rgba(255, 255, 255, 0.3)
11-
static const blackTransparent =
12-
Color(0x4D000000);
11+
static const blackTransparent = Color(0x4D000000);
1312

1413
static const lightColorScheme = ColorScheme(
1514
brightness: Brightness.light,

compass_app/app/lib/ui/core/ui/scroll_behavior.dart

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ import 'package:flutter/material.dart';
66
class AppCustomScrollBehavior extends MaterialScrollBehavior {
77
@override
88
Set<PointerDeviceKind> get dragDevices => {
9-
PointerDeviceKind.touch,
10-
// Allow to drag with mouse on Regions carousel
11-
PointerDeviceKind.mouse,
12-
};
9+
PointerDeviceKind.touch,
10+
// Allow to drag with mouse on Regions carousel
11+
PointerDeviceKind.mouse,
12+
};
1313
}

0 commit comments

Comments
 (0)