← → or space · progress saves for Continue on the roadmap
Goal
Wire a tiny auth-shaped stack with constructor injection and a single place that calls new (or factories).
Step 1 - ApiResponse and client contract
class ApiResponse<T> {
final int status;
final T? data;
final String? error;
ApiResponse({required this.status, this.data, this.error});
bool get isSuccess => status >= 200 && status < 300;
}
abstract class ApiClient {
Future<ApiResponse<Map<String, dynamic>>> post(
String path, {
Map<String, dynamic>? body,
});
}Step 2 - Fake HTTP client
class FakeApiClient implements ApiClient {
ApiResponse<Map<String, dynamic>>? next;
@override
Future<ApiResponse<Map<String, dynamic>>> post(
String path, {
Map<String, dynamic>? body,
}) async {
return next ?? ApiResponse(status: 500, error: 'unset');
}
}Step 3 - Repository
abstract class AuthRepository {
Future<ApiResponse<Map<String, dynamic>>> login(String email, String password);
}
class RemoteAuthRepository implements AuthRepository {
final ApiClient _api;
RemoteAuthRepository(this._api);
@override
Future<ApiResponse<Map<String, dynamic>>> login(String email, String password) {
return _api.post(
'/login',
body: <String, dynamic>{'email': email, 'password': password},
);
}
}Step 4 - Service
class AuthService {
final AuthRepository _repo;
AuthService(this._repo);
Future<bool> signIn(String email, String password) async {
final r = await _repo.login(email, password);
return r.isSuccess;
}
}Step 5 - Composition root
Future<void> main() async {
final api = FakeApiClient()
..next = ApiResponse(status: 200, data: {'token': 'abc'});
final repo = RemoteAuthRepository(api);
final auth = AuthService(repo);
print(await auth.signIn('a@b.com', 'x'));
}Practice tasks
- Add
HttpApiClient implements ApiClientwithdart:ioHttpClientorpackage:httpif you want real I/O. - Register the same graph in a
get_itsetup function without changingAuthService’s constructor. - Add
Result<bool>from Level 6 instead of bareboolforsignInoutcomes.