Flutter State Management with GetX - Complete App

Flutter GetX 사용해보기

Posted by Minki on November 6, 2020

들어가기

이전 포스팅에 이어 오늘은 Flutter State Management 중 하나인 GetX에 대해 알아보겠다. 글을 쓰는 시점 기준 GetX는 대중적인 Flutter StateMent 중 가장 최근에 나온 package이다. 그러나 그 뛰어난 성능으로 빠른 시간 안에 주목받게 되었다. 그래서 나도 공부해보고자 이번 포스팅을 준비했다. 그럼 지금부터 재미있게 읽어주셨으면 좋겠다.

이번 포스팅은 App with Flutter의 글을 번역한 것입니다. *잘못된 내용이 있으면 언제든지 밑의 댓글로 알려주세요.

1. Flutter GetX란?

어떤 패키지가 Flutter State Management에 가장 좋을까? 이 질문은 초심자뿐 아니라 숙력된 개발자들에게도 꽤나 머리아픈 질문이다.

이 질문에 어느정도 해답을 주기 위해 GetX의 개발자는 ListView에 아이템을 추가하는 벤치마크 실험을 진행했고, 그 결과를 다른 유명 State Management 라이브러리들과 비교했다. 그리고 그 결과는 아래의 그림에서 볼 수 있다.

위의 결과에 따라 GetX는 다른 패키지와 비교해보아도 준수한 성능을 보인다. 이를 통해 우리는 Flutter State Management에 사용할 수 있는 라이브러리가 하나 더 추가된 것이다. 게다가 다른 라이브러리와 비교했을때, GetX는 더 쉽고, 더 명료한 방법으로 State Management를 진행 할 수 있다.

또한 GetX를 통해 routing animation과 routing controll, 다른 Scaffold packages를 대체할 수 있으므로 GetX 패키지는 우리로 하여금 많은 라이브러리를 사용하지 않게끔 해준다. 그럼 지금부터 GetX Package에 대해 하나하나 살펴보자.

2. Overview of GetX Package

GetX package는 State Management 뿐만 아니라 다른 context methods, route, dynamic theme management, localization, dialog, bottom sheet 등 많은 기능을 대신해줄 수 있다. 그럼 지금부터 GetX가 개발자들에게 제공하는 기능을 살펴보자.

  • Replacement of Context.
  • Easy implementation for routes and dynamic routing.
  • Dynamic Theme
  • Dialog, BottomSheet, Snackbar implementation.
  • Data sharing between screen.
  • Utility Methods.

이처럼 GetX는 매우 쉽고, 안전하고, 빠르고, 강력한 Flutter State Management Package이다.

3. GetX Package Tutorial for App Development

지금부터 개발하는 앱에서는 원래 기존 Flutter Method를 GetX Method로 대체할 예정이다. 그리고 그 목록은 다음과 같다.

기존 Flutter Method GetX Method
MediaQuery.of(context).width Get.width
Navigator.push(context, NextScreen()) Get.to(NextScreen())
Flutter Dialog Get.dialog()
Sharing data between screen Get.find(ControllerName())

3-1. Get.width / Get.height

UI를 설계할때 우리는 App Screen의 넓이와 높이를 반드시 알아야 한다. 만약 기존 Flutter Method를 사용한다면, MediaQuery.of(context).width와 같은 형식으로 App Screen의 넓이를 호출해야 했으나, GetX Method를 사용한다면 Get.width와 같은 간단한 명령어로 호출이 가능하다. 따라서 기존 방식보다 더 생산적이고 빠르면서도 코드도 읽기 쉽다.

3-2. Get.to(NextScreen())

일반적으로 Flutter에서 nivigator를 사용한다면 약 4줄 정도의 코드가 필요했다. 그러나 GetX Method를 사용한다면 단 한 줄의 코드로 Screen간 navigation 기능을 동작하게 할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
// 기존 Flutter Method
onPressed: () {
    Navigator.push(
        context,
        MaterialPageRoute(builder: (context) => NextScreen()),
    )
}

// GetX Method
onPressed: () {
    Get.to(NextScreen())
}

3-3. Get.dialog()

GetX는 다른 alert options 기능을 가지고 있다. 이 경우도 마찬가지로 기존 Flutter Method와 비교해본다면 매우 빠른 생산력을 체감할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
// 기존 Flutter Method
showDialog(context: context, child:
    new AlertDialog(
        title: Text('title'),
        content: Text('Heloo World')
    )
);

// GetX Method
Get.dialog(DialogWidget());
//ex)
Get.dialog(CircularProgressIndicator(), barrierDismissible: false);

3-4. Get.find(ControllerName())

Screen간에 data를 교환하는 가장 쉬운 방법은 Constructor에서 받고자 하는 변수를 미리 argument로 설정한 다음에 보내고자 하는 변수를 Constructor로 직접 보내는 방법이다. 그러나 보내야하는 변수가 많아진다면 그만큼 받고자 하는 argument도 많아져야 하기에 이 방법은 매우 비효율적이다.

이때 Get.find()를 사용하면 다음 페이지 안의 controller를 통해 변수간 data 교환이 매우 수월해진다. 이 과정은 앱을 만들면서 더 자세히 살펴보도록 하자.

4. App with GetX Package

이제 실제로 GetX Package를 이용해 실제 Login Screen과 Home Screen으로 구성된 앱을 만들어볼 시간이다. Login Screen에는 간단하게 TextFromFiled를 이용한 데모 로그인 기능을 구현할 것이고, Home Screen에서는 User 정보를 Api로부터 받아와 이를 List로 보여주는 기능을 구현할 것이다.

먼저 시작하기 전에 다음의 Package를 pubspec.yaml에 입력해주자.

1
2
3
get: ^3.15.0
http: ^0.12.2
google_fonts: ^1.1.1

4-1. file structre of our application

  • The controller folder contains all the business logic that we well write using GetX package
  • The View folder contains all the screens
  • The http folder contains URL and API call login
  • The model folder contains the POJO for our HTTP response
  • The utility folder contains the router file where we will define all our routings.

4-2. main.dart file

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:google_fonts/google_fonts.dart';
import './utility/router.dart';

void main() {
  WidgetsFlutterBinding.ensureInitialized();
  runApp(GetMaterialApp(
    title: 'GetX App',
    debugShowCheckedModeBanner: false,
    // defaultTransition is used for navigating with animation.
    defaultTransition: Transition.rightToLeft,
    // getPages is the parameter that will hold all of the routes.
    getPages: GetXRouter.route,
    // initialRoute is used to specify the launch screen of your application
    initialRoute: 'loginView',
    theme: ThemeData(
        primarySwatch: Colors.deepOrange,
        appBarTheme: AppBarTheme(
            color: Colors.deepOrange,
            textTheme: TextTheme(
                headline6: GoogleFonts.exo2(
              color: Colors.white,
              fontSize: 18,
              fontWeight: FontWeight.bold,
            )))),
  ));
}

main.dart에서 눈여겨 봐야할 것은 MaterialApp대신에 GetMaterialApp을 앱 최상단에 위치시켜 준다는 것이다. 그러나 항상 GetMaterialApp를 써야하는 것은 아니다. 만약 GetX 패키지를 이용해 Navigation 기능까지 사용하고 싶으면 GetMaterialApp을 사용해야 하지만, 단순히 State Management로만 사용할 계획이라면 MaterialApp으로 감싸도 무방하다. 위에서 사용한 몇몇 명령어를 알아보자.

  • defaltTransition : Navigating animation을 설정.
  • getPages : GetX parameter that will hold all of the rotes.
  • initialRoute : 앱의 시작 화면 설정.

4-3. GetX Navigator

1
2
3
4
5
6
7
8
9
10
import 'package:get/get.dart';
import '../view/home_view.dart';
import '../view/login_view.dart';

class GetXRouter {
  static final route = [
    GetPage(name: 'loginView', page: () => LoginView()),
    GetPage(name: 'homeView', page: () => HomeView())
  ];
}

다음으로는 utility folder 안에 router.dart 파일을 생성해보자. 이 파일의 목적은 앱의 모든 Navigation을 관리하는 것이다. 또한 GetPage안에 trainsition parameter를 이용하여 Page로 이동할때마다 다른 animation이 수행되도록 할 수 있다. 또한 GetX를 사용해서 Navigator를 수행하는 방법은 많은데 하나하나 살펴보면 다음과 같은 것들이 있다.

1
2
Get.to(HomeView())
Get.toNamed('homeview')
  • go to HomeView Screen
1
Get.off(HomeView())
  • HomeView Screen으로 가는데, 이전 스크린으로 되돌아 가는 option을 원치 않을때 사용. SplashScreen, login Screen에 사용하면 좋다.
1
Get.offAll(HomeView())
  • HomeView Screen으로 가는데, 모든 이전 routes를 초기화 시키는 것. 장바구니, 투표, test 등에 유용함.
1
Get.back()
  • To close snackbars, dialogs, bottomsheets or anything. Navigator.pop(context)와 같은 기능.

4-4. url.dart, request.dart inside http folder.

1
2
3
4
5
<url.dart>

const urlBase = 'https://reqres.in/api/';
const urlLogin = 'login';
const urlUserList = 'users?page=1';

앱의 user 정보를 가져오기 위해 url.dart에서 API 주소를 정의하고, 이를 request.dart에서 API를 호출하는 구조이다. url 정보를 const로 설정해, final과 같이 url 값이 변경되는 것을 막도록 하였고, 이를 컴파일 상수로 선언하였다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<request.dart>

import '../http/url.dart';
import 'package:http/http.dart' as http;

class Request {
  final String url;
  final dynamic body;

  Request({this.url, this.body});

  Future<http.Response> post() {
    return http.post(urlBase + url, body: body).timeout(Duration(minutes: 2));
  }

  Future<http.Response> get() {
    return http.get(urlBase + url).timeout(Duration(minutes: 2));
  }
}

request.dart에서는 post()get() method를 따로따로 정의했는데 get()은 userList 정보를 가져오는 method이고, post()는 user의 email과 login 정보를 가져오는 method이다. 그럼 이제 본격적으로 User List Json을 parse하는 클래스를 생성해보자.

4-5. User Data Json parse

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
<user_list_model.dart>

import '../model/userData.dart';
import '../model/adData.dart';

class UserListModel {
  int page;
  int perPage;
  int total;
  int totalPages;
  List<UserData> data;
  AdData ad;

  UserListModel(
      {this.page,
      this.perPage,
      this.total,
      this.totalPages,
      this.data,
      this.ad});

  // UserListModel의 argument에 'https://reqres.in/api/login'에서 불러온 json 데이터를 할당.
  UserListModel.fromJson(Map<String, dynamic> json) {
    page = json['page'];
    perPage = json['per_page'];
    total = json['total'];
    totalPages = json['total_pages'];
    if (json['data'] != null) {
      // List<UserData> type의 빈 List를 만들어주는것.
      data = new List<UserData>();
      // json['data']가 null이 아니면 json['data']에 List<UserData>가 들어가있음.
      // 이를 forEach를 이용해 data에 넣어주는 것.
      // UserData의 fromJson 함수를 이용해 data[1].id와 같은 형식으로 data를 return할 수 있음
      json['data'].forEach((v) {
        data.add(UserData.fromJson(v));
      });
    }
    // ad는 List가 아니므로 그냥 List<AdData>가 아닌 AdData로 type을 지정해야함.
    // AdData의 fromJson 형식을 통해 ad.company와 같은 형식으로 data를 불러올 수 있음.
    ad = json['ad'] != null ? AdData.fromJson(json['ad']) : null;
  }

  // 이거는 return이 있기 때문에 getter와 같은 역할
  // fromJson에서 data와 ad에 올바른 형식의 데이터를 담고 이제 이 함수로 불러와주는 것.
  // UserListModel.toJson()으로 호출
  Map<String, dynamic> toJson() {
    // 빈 Map<String, dynamic> type의 data를 만들어줌.
    final Map<String, dynamic> data = Map<String, dynamic>();
    data['page'] = this.page;
    data['per_page'] = this.perPage;
    data['total'] = this.total;
    data['total_pages'] = this.totalPages;
    if (this.data != null) {
      data['data'] = this.data.map((v) => v.toJson()).toList();
    }
    if (this.ad != null) {
      data['ad'] = this.ad.toJson();
    }
    return data;
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<adData.dart>

class AdData {
  String company;
  String url;
  String text;

  AdData({this.company, this.url, this.text});

  AdData.fromJson(Map<String, dynamic> json) {
    company = json['company'];
    url = json['url'];
    text = json['text'];
  }

  // 이거는 return이 있기 때문에 getter와 같은 역할
  // fromJson에서 AdData에 값을 담고 이 getter를 통해 데이터를 호출
  // AdData.toJson()으로 호출
  Map<String, dynamic> toJson() {
    final Map<String, dynamic> data = Map<String, dynamic>();
    data['company'] = this.company;
    data['url'] = this.url;
    data['text'] = this.text;
    return data;
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<userData.dart>

class UserData {
  int id;
  String email;
  String firstName;
  String lastName;
  String avatar;

  UserData({this.id, this.email, this.firstName, this.lastName, this.avatar});

  UserData.fromJson(Map<String, dynamic> json) {
    id = json['id'];
    email = json['email'];
    firstName = json['first_name'];
    lastName = json['last_name'];
    avatar = json['avatar'];
  }

  // 이거는 return이 있기 때문에 getter와 같은 역할
  // fromJson에서 UserData에 값을 담고 이 getter를 통해 데이터를 호출
  // UserData.toJson()으로 호출
  Map<String, dynamic> toJson() {
    final Map<String, dynamic> data = Map<String, dynamic>();
    data['id'] = this.id;
    data['email'] = this.email;
    data['first_name'] = this.firstName;
    data['last_name'] = this.lastName;
    data['avatar'] = this.avatar;
    return data;
  }
}

설명은 주석으로 달아놨다. 만약 이해가 어렵다면 세 가지 파일 모두 data가 return 되는 method가 최종 method이므로 거꾸로 올라가면서 데이터의 흐름을 파악하면 이해하는데 도움이 되리라 생각한다.

4-6. login_view.dart

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:google_fonts/google_fonts.dart';
import '../controller/login_controller.dart';

class LoginView extends StatelessWidget {
  // GetX State Management로 Controller를 불러오는 과정
  // _loginController안에 login과 관련된 정보가 있고 이를 앱 전반에서 공유
  final LoginController _loginController = Get.put(LoginController());
  final _formKey = GlobalKey<FormState>();
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.white,
      appBar: AppBar(
        title: Text('GetX Login'),
      ),
      body: Container(
        padding: EdgeInsets.all(16),
        child: Form(
          key: _formKey,
          child: Column(
            children: [
              TextFormField(
                controller: _loginController.emailTextController,
                keyboardType: TextInputType.emailAddress,
                decoration: InputDecoration(
                    fillColor: Colors.grey[200],
                    filled: true,
                    hintText: 'Email',
                    hintStyle: GoogleFonts.exo2(
                        fontSize: 16,
                        color: Colors.grey,
                        fontWeight: FontWeight.normal),
                    border: OutlineInputBorder(
                        borderRadius: BorderRadius.circular(10),
                        borderSide:
                            BorderSide(color: Colors.transparent, width: 0)),
                    enabledBorder: OutlineInputBorder(
                        borderRadius: BorderRadius.circular(10),
                        borderSide:
                            BorderSide(color: Colors.transparent, width: 0))),
                style: GoogleFonts.exo2(
                  fontSize: 16,
                  color: Colors.black,
                  fontWeight: FontWeight.normal,
                ),
                validator: (value) =>
                    value.trim().isEmpty ? 'Email required' : null,
              ),
              SizedBox(height: 16),
              TextFormField(
                controller: _loginController.passwordTextController,
                keyboardType: TextInputType.text,
                obscureText: true,
                decoration: InputDecoration(
                    fillColor: Colors.grey[200],
                    filled: true,
                    hintText: 'PassWord',
                    hintStyle: GoogleFonts.exo2(
                        fontSize: 16,
                        color: Colors.grey,
                        fontWeight: FontWeight.normal),
                    border: OutlineInputBorder(
                        borderRadius: BorderRadius.circular(10),
                        borderSide:
                            BorderSide(color: Colors.transparent, width: 0)),
                    enabledBorder: OutlineInputBorder(
                        borderRadius: BorderRadius.circular(10),
                        borderSide:
                            BorderSide(color: Colors.transparent, width: 0))),
                validator: (value) =>
                    value.trim().isEmpty ? 'Password required' : null,
                style: GoogleFonts.exo2(
                    fontSize: 16,
                    color: Colors.black,
                    fontWeight: FontWeight.normal),
              ),
              SizedBox(height: 16),
              MaterialButton(
                color: Colors.deepOrangeAccent,
                splashColor: Colors.white,
                height: 45,
                minWidth: Get.width / 2,
                shape: RoundedRectangleBorder(
                    borderRadius: BorderRadius.circular(10)),
                child: Text(
                  'Login',
                  style: GoogleFonts.exo2(
                      fontSize: 20,
                      color: Colors.white,
                      fontWeight: FontWeight.bold),
                ),
                onPressed: () {
                  if (_formKey.currentState.validate()) {
                    _loginController.apiLogin();
                  }
                },
              )
            ],
          ),
        ),
      ),
    );
  }
}

해당 dart 파일의 상단에 위치한 final LoginController _loginController = Get.put(LoginController());이 GetX State Management 부분이다. _loginController에 login과 관련된 모든 정보들이 있고 이를 GetX를 통해 앱 전반에서 공유하는 구조이다.

4-6. Home_view.dart

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../controller/home_controller.dart';
import '../controller/login_controller.dart';
import 'package:google_fonts/google_fonts.dart';

class HomeView extends StatelessWidget {
  final LoginController _loginController = Get.find();
  final HomeController _homeController = Get.put(HomeController());

  @override
  Widget build(BuildContext context) {
    print(_loginController.emailTextController.text);
    return Scaffold(
      backgroundColor: Colors.white,
      appBar: AppBar(
        title: Text('DashBoard'),
      ),
      body: Container(
        padding: EdgeInsets.all(16),
        child: Column(
          children: [
            Text(
              'Welcome ${_loginController.emailTextController.text}',
              style: GoogleFonts.exo2(
                  fontSize: 16,
                  color: Colors.black,
                  fontWeight: FontWeight.normal),
            ),
            SizedBox(
              height: 16,
            ),
            Expanded(
                child: Obx(() => ListView.builder(
                    itemCount: _homeController.userListData.length,
                    itemBuilder: (context, index) => ListTile(
                        title:
                            Text(_homeController.userListData[index].firstName),
                        subtitle:
                            Text(_homeController.userListData[index].email),
                        trailing: Image.network(
                          _homeController.userListData[index].avatar,
                          width: 80,
                          height: 80,
                          fit: BoxFit.contain,
                        ),
                        leading: IconButton(
                          icon: Icon(
                            Icons.delete,
                            color: Colors.black,
                          ),
                          onPressed: () {
                            _homeController.deleteItem(index);
                          },
                        )))))
          ],
        ),
      ),
    );
  }
}

homeView.dart파일 역시 Class의 상단에 위치한 final HomeController _homeController = Get.put(HomeController());를 통해 User List 정보의 State Management를 실시한다. 여기서 또 하나 주목해야할 점은 Get.find()이다.

Get.find()는 Get에게 다른 페이지에서 쓰인 Controller를 찾아서 redirect를 부탁하는 명령어이다. 따라서 Get.find()를 사용하면 다른 페이지에서 사용되고 있는 Controller를 해당 page에서 다시 사용할 수 있다.

따라서 final LoginController _loginController = Get.find();명령어는 login_view.dart에서 사용하고 있는 _loginController를 찾아서 해당 페이지에서 redirect하는 것이다. 그럼으로써 home_view.dart파일에서도 _loginController를 통해 로그인 정보를 사용할 수 있게 된다.

이 페이지에서 Get.put()을 사용하지 않고 Get.find()를 사용한 이유는 해당 페이지에서 필요로 하는 정보는 오직 emailTextController이기 때문이다. 따라서 무거운 명령어 대신에 가벼운 명령어를 사용했다 생각하면 받아들이기 편할 것이다.

4-6. login_controller.dart

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:getx_app/http/request.dart';
import 'package:getx_app/http/url.dart';

class LoginController extends GetxController {
  TextEditingController emailTextController;
  TextEditingController passwordTextController;

  @override
  void onInit() {
    emailTextController = TextEditingController();
    passwordTextController = TextEditingController();
    super.onInit();
  }

  void apiLogin() async {
    Get.dialog(Center(child: CircularProgressIndicator()),
        barrierDismissible: false);
    Request request = Request(url: urlLogin, body: {
      'email': emailTextController.text,
      'password': passwordTextController.text
    });
    request.post().then((value) {
      Get.back();
      Get.offNamed('homeView');
    }).catchError((onError) {});
  }

  @override
  void onClose() {
    emailTextController?.dispose();
    passwordTextController?.dispose();
    super.onClose();
  }
}

이제 본격적으로 GetXController를 어떻게 다루는지 살펴볼 차례이다. 먼저 Provider와 매우 유사하게 Class를 생성한 다음에 GetXController를 extends 해주는 것으로 시작한다.

그 다음 TextFormField의 Text를 Control하기 위해 email과 password 각각 TextEditingController를 생성해준다.

그 후 void onInit()를 통해 TextEditingController의 생성자를 만들어주는데 이때, void onInit()void initState()와 같은 method로 login_view에서 final LoginController _loginController = Get.put(LoginController());를 호출할때, 바로 void onInit()안에 있는 코드들이 실행된다.

또한 apiLogin()에서는 rest API를 호출하기 전에 Get.dialog를 이용해 Loading Animation을 생성해준다. 그 후 request.post()가 호출이 완료되면 API로부터 email과 password를 얻음과 동시에 then method를 통해 다음의 명령들이 실행된다.

  • Get.back() : Get.dialog를 꺼주는 명령어.
  • Get.offNamed(‘homeView’) : home_view Screen으로 Navigator 해주는 명령어.

이와 같은 명령들이 실행되고나면 마지막으로 void onClose()가 실행된다. 이 명령어는 dispose와 같은 명령어로 emailTextController와 passwordTextController를 없애주는 역할을 한다. 없애주는 이유는 home_view에서도 역시 _loginController.emailTextController를 호출하는데 그렇게되면 동일한 emailTextController 및 passwordTextController 가 충돌할 수도 있기 때문이다. 따라서 이를 막기 위해 다 쓴 Controller를 onClose를 통해 없애준다.

4-7. home_controller.dart

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:getx_app/http/request.dart';
import 'package:getx_app/http/url.dart';
import 'package:getx_app/model/user_list_model.dart';

class HomeController extends GetxController {
  var userListData = List<Data>().obs;
  @override
  void onInit() {
    _apiGetUserList();
    super.onInit();
  }

  void _apiGetUserList() async {
    Future.delayed(
        Duration.zero,
        () => Get.dialog(Center(child: CircularProgressIndicator()),
            barrierDismissible: false));
            
    Request request = Request(url: urlUserList, body: null);
    request.get().then((value) {
      UserListModel userListModel =
          UserListModel.fromJson(json.decode(value.body));
      userListData.value = userListModel.data;
      Get.back();
    }).catchError((onError) {
      print(onError);
    });
  }

  void deleteItem(int index) {
    userListData.removeAt(index);
  }
}

다음은 HomeController이다. 여기서는 이전 LoginController에서는 못보던 obs 명령어가 있는데 이는 provider에서 notifyListner()와 같은 역할이다. 따라서 .obs로 선언된 객체들은 GetX가 지속적으로 변수의 변화를 관찰한다. 그리고 변수가 변한다면 이를 UI에 반영해준다.

따라서 home_view에 생성된 UserList에서 삭제 버튼을 통해 User를 삭제한다면, obs가 이 변화를 캐치해서 바뀐 userList로 UI를 다시 build해준다. 나머지는 LoginController와 거의 동일하기에 별도의 설명은 생략하겠다.

5. 마무리

이번 Tutorial을 따라해봤으면 알겠지만, GetX는 Provider만큼 쉽고, 여기에 더해 Navigator 기능 또한 가지고 있다. 따라서 GetX를 사용한다면, package와 코드를 훨씬 더 많이 절약할 수 있다는 생각이 든다. 따라서 나는 개인적으로 앞으로 Provider보다는 GetX를 주로 사용할것 같다.


  • 깃허브 코드는 여기에 있습니다.


reference

  • https://www.appwithflutter.com/flutter-state-management-with-getx/