MVP Pattern in Flutter with (Un-official) Retrofit

Pada artikel ini, saya akan menjelaskan sedikit tentang penerapan MVP Pattern pada Flutter, sebagaimana diketahui pada Rekadia sendiri untuk development aplikasi Android telah menggunakan pattern ini juga.

Namun saya sedikit minta maaf jika banyak dari yang saya sampaikan agak keliru ataupun kurang tepat nantinya karena saya masih dalam hitungan hari saat menulis artikel ini. Kritik & saran yang membangun dari Anda sangat dibutuhkan 🙂

1. Import semua library yang dibutuhkan pada pubspec.yaml

dependencies:
  flutter:
    sdk: flutter

  cupertino_icons: ^1.0.0

//libraries
  http: ^0.12.2
  dio: 3.0.10
  json_serializable: ^3.5.0
  auto_data: 0.0.3
  json_annotation: 3.1.0
  retrofit_generator: ^1.3.8+5
  build_runner:


dev_dependencies:
  flutter_test:
    sdk: flutter
  auto_data_generator: ^0.0.3

  • Dio dan Http berfungsi sebagai library yang akan mengurus segala hal terkait dengan server.
  • Json serializable & annotation sebagai library generator untuk JSON format.
  • Auto data untuk generator class data nantinya.
  • Build runner untuk menjalankan codecode generator lainya.

2. Converter untuk Response dari server

Disini akan terdapat 2 type converter yang dibutuhkan yaitu List dan Object, langsung kita buat saja satu persatu :

  1. response_object.dart
part 'response_object.g.dart';

@JsonSerializable(genericArgumentFactories: true)
class ResponseObject<T> extends Object{
  T data;
  String message;
  String status;

  ResponseObject({this.data, this.message, this.status});

  factory ResponseObject.fromJson(Map<String, dynamic> json, Object object) => _$ResponseObjectFromJson<T>(json, object);
  Map<String, dynamic> toJson(Function(Object json) toJson) => _$ResponseObjectToJson(this, toJson);
}

Perlu dicatat untuk setiap class yang membutuhkan generator code harus kita definisikan fileName nya dengan part, misalnya : part ‘response_object.g.dart’;

Lalu untuk meng-generate file part-nya cukup buka Terminal dan tuliskan command :

flutter pub run build_runner build

Untuk semua file yang memerlukan code generator, setiap kita merubah file didalamnya kita diharuskan menjalankan command diatas melalui terminal. No shortcut , no compromise.

Meski code yang telah berhasil di generate tetap bisa kita lihat dan edit, namun dianjurkan jangan kita edit. Jika ingin merubah cukup melalui class part-of nya saja. Ikuti aturan yang sudah cape-cape bikin librarynya 🙂

Hasilnya seperti dibawah :

2. Buat class response_list.dart

part 'response_list.g.dart';

@JsonSerializable()
class ResponseList{
  int total;
  String status;
  String message;
  List<dynamic> data;

  ResponseList();

  factory ResponseList.fromJson(Map<dynamic, dynamic> json) => _$ResponseListFromJson(json);
  Map<dynamic, dynamic> toJson() => _$ResponseListToJson(this);

}

Namun penggunaan response list ini mungkin akan dijelaskan pada artikel berikutnya, selagi saya masih explore untuk pagination , mengingat saya masih awam di Flutter.

3. Buat Object Class untuk Request dan Response

  1. user_request.dart :
part: 'user_request.g.dart';

@data
class $UserRequest {
  final String nik;
  final String deviceId;
  final String deviceType;
  final String deviceName;
  final String deviceToken;
  final int osVersion;
}

Untuk generator code kali ini saya menggunakan auto_data, dikarenakan saya rasa didalamnya cukup lengkap fungsi yang disediakan dari generator-nya.

Berikut saya sertakan hasil dari generator auto_data :

2. user_response.dart :

@data
class $UserResponse {
  final String userId;
  final String nik;
  final String email;
  final String fullName;
  final String workerCategory;
  final String manageCategory;
  final String photoUri;
  final bool hasKpi;
  final int workerCategoryId;
  final int manageCategoryId;
  final List<String> roles;
}

4. Buat API class

  1. user_api.dart :
part 'user_api.g.dart';

@RestApi(baseUrl: "https://rekadia.net/gbk.osetrack/Api/User/")
abstract class RestClient {
  factory RestClient(Dio dio) = _RestClient;


  @FormUrlEncoded()
  @POST("login")
  Future<ResponseObject<UserResponse>> login(@Header("Api-key") String apiKey, @Body() UserRequest body);
}

Untuk class API saya menggunakan code generator dari library Retrofit, namun perlu diingat library ini bukan dibuat dari official Retrofit, jadi mungkin kedepanya saya cukup menggunakan Dio saja dan jika menemukan library yang cocok akan saya kombinasikan dengan Dio.

Lalu untuk return nya ber-type Future, karena meskipun menggunakan RxDart nantinya di dokumentasinya sudah dijelaskan sebagai berikut bahwa Single/Maybe dan Completable akan menggunakan Future dari Dart.

Untuk service sendiri saya mencoba dengan memanfaatkan service yang sudah ada yaitu service login dari GBK Osetrack yang sudah ada di server Rekadia. Dan mohon maaf jika sisa dari artikel ini banyak hardcode karena tujuanya hanya untuk implementasi tentang Pattern MVP nya saja.

5. MVP things

  1. login_view.dart :
class LoginInteractor {
  void login(UserRequest request){}
  void onSuccessLogin(String userId){}
  void destroy(){}
}

class LoginView{
  void toast(String message){}
}

Class login_view.dart berisi contract dari methodmethod yang akan kita implementasikan nantinya.

2. login_presenter.dart :

import 'login_view.dart';

class LoginPresenter implements LoginInteractor {
  LoginView view;
  RestClient api = RestClient(Dio());
  LoginPresenter(this.view);

  @override
  void destroy() => view = null;

  @override
  void login(UserRequest request) async {

    await api.login(apiKey, request).then((it) {
      if(it.status == "I"){
        if(it.data is UserResponse){
          this.onSuccessLogin(it.data.toString());
        } else{
       view?.toast("Object is not defined");
       }
      } else{
        view?.toast(it.message);
      }
    }).catchError((e){
      view?.toast(e.toString());
    });
  }

  @override
  void onSuccessLogin(String userLogin) async {
    SharedPreferences prefs = await SharedPreferences.getInstance();
    await prefs.setString("userLogin", userLogin);
  }
}

3. login_activity.dart :

import 'login_view.dart';

class LoginActivity extends StatefulWidget {
  @override
  _LoginActivityState createState() => _LoginActivityState();
}

class _LoginActivityState extends State<LoginActivity> implements LoginView{
  LoginPresenter presenter;
  TextEditingController nikController = TextEditingController();

  @override
  void initState() {
    super.initState();
    presenter = LoginPresenter(this);
  }

  void doLogin(String email, String password){
    var user = UserRequest(nik:"778899",
        deviceId:"ffffffff-c978-5f8f-ffff-ffff8b3704d9",
        deviceType:"Android",
        deviceName:"SAMSUNG SM-G955U",  deviceToken:"fFI9GO_URdyDIrHbG1uu6S:APA91bEN5cm6yGOgWP3IizM9SrZmYUw7qlzUTXIMlwDZ0a3Auc6kXY6MrCg7XaMHfQ7kMn4OTRDL3Ys_Zb8uxoNGXX62pGhJVizOwJvGqbmUVwt8frEX1fs81PEjhZEkAmVYdN_NrGWr",
        osVersion:int.parse("26"));
    presenter.login(user);
  }

@override
  Widget build(BuildContext context) {
    return Scaffold(
       //UI
   );

  }

@override
  void toast(String message) => Toast.show(message, context, duration: Toast.LENGTH_LONG, gravity: Toast.BOTTOM);

}

Untuk UI code-nya saya skip karena tidak terlalu dibutuhkan untuk context pada artikel ini.

Semua class sudah lengkap dan bisa kita test :

Sekian artikel dari saya, CMIIW 🙂

Rizky Agung Ramadhan has written 10 articles

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>