Skip to content

Commit 3fab74d

Browse files
Copilottrevorwang
andauthored
Add dart_mappable parser support for type conversion (#847)
* Initial plan * Add DartMappable parser support to retrofit Co-authored-by: trevorwang <121966+trevorwang@users.noreply.github.com> * Add example_dartmappable and remove backup file Co-authored-by: trevorwang <121966+trevorwang@users.noreply.github.com> * Add dart_mappable documentation and gitignore Co-authored-by: trevorwang <121966+trevorwang@users.noreply.github.com> * Add generated example and update Parser enum test Co-authored-by: trevorwang <121966+trevorwang@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: trevorwang <121966+trevorwang@users.noreply.github.com>
1 parent 413cd76 commit 3fab74d

12 files changed

Lines changed: 649 additions & 3 deletions

File tree

README.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,49 @@ enum Status {
139139
Future<List<Task>> getTasksByStatus(@Path() Status status);
140140
```
141141

142+
#### Using dart_mappable
143+
144+
You can use [dart_mappable](https://pub.dev/packages/dart_mappable) for type conversion by setting the parser to `Parser.DartMappable`:
145+
146+
```dart
147+
@RestApi(
148+
baseUrl: 'https://api.example.com',
149+
parser: Parser.DartMappable,
150+
)
151+
abstract class ApiService {
152+
factory ApiService(Dio dio) = _ApiService;
153+
154+
@GET('/tasks')
155+
Future<List<Task>> getTasks();
156+
}
157+
158+
@MappableClass()
159+
class Task with TaskMappable {
160+
const Task({this.id, this.name});
161+
162+
final String? id;
163+
final String? name;
164+
}
165+
```
166+
167+
Don't forget to add the required dependencies:
168+
169+
```yaml
170+
dependencies:
171+
dart_mappable: ^4.2.0
172+
173+
dev_dependencies:
174+
dart_mappable_builder: ^4.2.0
175+
```
176+
177+
And generate the code:
178+
179+
```sh
180+
dart run build_runner build
181+
```
182+
183+
For a complete example, see the [example_dartmappable](https://github.com/trevorwang/retrofit.dart/tree/master/example_dartmappable) directory.
184+
142185
#### Typed extras
143186
If you want to add static extra to all requests.
144187

example_dartmappable/.gitignore

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Files and directories created by pub.
2+
.dart_tool/
3+
.packages
4+
build/
5+
pubspec.lock
6+
7+
# Directory created by dartdoc
8+
doc/api/
9+
10+
# IDEs
11+
.idea/
12+
.vscode/
13+
*.iml
14+
*.ipr
15+
*.iws

example_dartmappable/README.md

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
# Dart Mappable Example
2+
3+
This example demonstrates how to use `dart_mappable` with Retrofit for type conversion.
4+
5+
## Setup
6+
7+
1. Add the required dependencies to your `pubspec.yaml`:
8+
9+
```yaml
10+
dependencies:
11+
retrofit: ^4.9.0
12+
dio: ^5.7.0
13+
dart_mappable: ^4.2.0
14+
15+
dev_dependencies:
16+
retrofit_generator: ^10.0.1
17+
build_runner: ^2.4.0
18+
dart_mappable_builder: ^4.2.0
19+
```
20+
21+
2. Define your models with `@MappableClass()`:
22+
23+
```dart
24+
import 'package:dart_mappable/dart_mappable.dart';
25+
26+
part 'example.mapper.dart';
27+
28+
@MappableClass()
29+
class Task with TaskMappable {
30+
const Task({
31+
this.id,
32+
this.name,
33+
this.avatar,
34+
this.createdAt,
35+
});
36+
37+
final String? id;
38+
final String? name;
39+
final String? avatar;
40+
final String? createdAt;
41+
}
42+
```
43+
44+
3. Create your API client with `Parser.DartMappable`:
45+
46+
```dart
47+
import 'package:retrofit/retrofit.dart';
48+
49+
part 'example.g.dart';
50+
51+
@RestApi(
52+
baseUrl: 'https://api.example.com',
53+
parser: Parser.DartMappable,
54+
)
55+
abstract class ApiService {
56+
factory ApiService(Dio dio) = _ApiService;
57+
58+
@GET('/tasks')
59+
Future<List<Task>> getTasks();
60+
}
61+
```
62+
63+
4. Generate code:
64+
65+
```bash
66+
dart run build_runner build
67+
```
68+
69+
## Running the Example
70+
71+
```bash
72+
dart run bin/main.dart
73+
```
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
include: package:lints/recommended.yaml
2+
3+
analyzer:
4+
exclude:
5+
- "**/*.g.dart"
6+
- "**/*.mapper.dart"
7+
8+
linter:
9+
rules:
10+
- avoid_print

example_dartmappable/bin/main.dart

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import 'package:dio/dio.dart';
2+
import 'package:logger/logger.dart';
3+
import 'package:retrofit_example_dartmappable/example.dart';
4+
5+
final logger = Logger();
6+
7+
Future<void> main(List<String> args) async {
8+
final dio = Dio();
9+
dio.options.headers['Demo-Header'] = 'demo header';
10+
dio.options.headers['Content-Type'] = 'application/json';
11+
final client = ApiService(dio);
12+
13+
// Get all tasks
14+
client.getTasks().then((it) => logger.i(it));
15+
16+
// Get a single task
17+
client.getTask('2').then((it) => logger.i(it)).catchError((Object obj) {
18+
switch (obj.runtimeType) {
19+
case DioException _:
20+
final res = (obj as DioException).response;
21+
logger.e('Got error : ${res?.statusCode} -> ${res?.statusMessage}');
22+
default:
23+
}
24+
});
25+
26+
// Create a new task
27+
client.createTask(const Task(avatar: '2222.png', name: 'new task')).then((it) {
28+
logger.i(it.toMap());
29+
});
30+
31+
// Update a task
32+
client
33+
.updateTask('3', const Task(id: '4', avatar: '1.png', name: 'number 3'))
34+
.then((it) {
35+
logger.i(it.toMap());
36+
});
37+
38+
// Delete a task
39+
client.deleteTask('2').then((it) {
40+
logger.i('Task 2 has been deleted!');
41+
}).catchError((Object err) {
42+
logger.e(err);
43+
});
44+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import 'package:dart_mappable/dart_mappable.dart';
2+
import 'package:dio/dio.dart' hide Headers;
3+
import 'package:retrofit/retrofit.dart';
4+
5+
part 'example.mapper.dart';
6+
part 'example.g.dart';
7+
8+
@RestApi(
9+
baseUrl: 'https://5d42a6e2bc64f90014a56ca0.mockapi.io/api/v1/',
10+
parser: Parser.DartMappable,
11+
)
12+
abstract class ApiService {
13+
factory ApiService(
14+
Dio dio, {
15+
String? baseUrl,
16+
ParseErrorLogger? errorLogger,
17+
}) = _ApiService;
18+
19+
@GET('/tasks')
20+
Future<List<Task>> getTasks();
21+
22+
@GET('/tasks/{id}')
23+
Future<Task> getTask(@Path('id') String id);
24+
25+
@POST('/tasks')
26+
Future<Task> createTask(@Body() Task task);
27+
28+
@PUT('/tasks/{id}')
29+
Future<Task> updateTask(@Path() String id, @Body() Task task);
30+
31+
@DELETE('/tasks/{id}')
32+
Future<void> deleteTask(@Path() String id);
33+
}
34+
35+
@MappableClass()
36+
class Task with TaskMappable {
37+
const Task({
38+
this.id,
39+
this.name,
40+
this.avatar,
41+
this.createdAt,
42+
});
43+
44+
final String? id;
45+
final String? name;
46+
final String? avatar;
47+
final String? createdAt;
48+
}

0 commit comments

Comments
 (0)