Notice
Recent Posts
Recent Comments
Link
«   2024/05   »
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
Archives
Today
Total
05-17 00:00
관리 메뉴

nomad-programmer

[Programming/Flutter] Provider 메소드 확장 (select, read, watch) 본문

Programming/Flutter

[Programming/Flutter] Provider 메소드 확장 (select, read, watch)

scii 2020. 10. 28. 12:53

Provider 라이브러리 4.1.0 이상의 버전에서는 더욱 간편하고 적은 비용으로 Provider를 사용할 수 있다. 이것은 최근 Dart 언어의 업데이트로 인하여 Provider 라이브러리도 업데이트가 된 사항이다.

Provider 코드가 상당히 많이 줄어들어 인상적인 업데이트다. 먼저 Dart 언어의 업데이트 내역을 살펴본 후 Provider를 살펴보도록 하자.

Dart 

dart extension은 다른 패키지 클래스에 속성과 메서드를 추가할 수 있다. 다음의 예를 보자.

// 열거형을 비롯해 다른 모든 클래스를 확장할 수 있다.
enum Connectivity { connected, disconnected, searching }

// 열거형에 메서드를 확장하였다.
extension on Connectivity {
  String humanize() => this.toString().split('.').last;
}

// int 클래스에 메서드를 확장하였다.
extension on int {
  int double() => this * 2;
}

void main() {
  print(Connectivity.connected.humanize());
  print(5.double());
}

/* 결과

connected
10

*/

위의 예제 코드에서 보듯이, extension 키워드를 이용하여 다른 클래스에 속성과 메서드를 추가할 수 있다. 


Provider

BuildContext는 위젯 트리에서 위젯 자체 위치에 대한 참조를 보유하고 있는 객체이다. 이것은 Provider에서 다음과 같이 사용된다.

Provider.of<String>(context);

"of" 메서드는 위젯 트리를 조회하여 <String> 유형을 찾는 메서드다. 이 of 메서드는 위젯 트리에서 위젯을 찾아야하는데 최악의 경우 위젯 트리의 최상위로 갈 수도 있다. 그러면 많은 프로세스 비용이 든다. 

최근 업데이트된 Provider는 이런 of 메서드를 사용하지 않고, 더 나은 방법을 제공한다.

  • BuildContext.read
  • BuildContext.watch
  • BuildContext.select

이 메서드들은 이전에 장황했던 Provider를 단순화 시켜준다.

New Old
BuildContext.read<Person>() Provider.of<Person>(context, listen: false)
BuildContext.watch<Person>() Provider.of<Person>(context)
BuildContext.select<Person> Selector

BuildContext.read<Person>() 메서드는 Provider.of<Person>(context, listen: false) 메서드를 대체한다. 이것은 Person 객체를 찾은 다음 반환한다.

BuildContext.watch<Person>() 메서드는 Provider.of<Person>(context) 메서드를 대체한다. watch 메서드는 ChangeNotifierProvider, FutureProvider, StreamProvider에서 사용된다.

BuildContext.select<Person> 메서드는 Selector를 대체한다. 이번 업데이트에서 이것이 가장 유용할 것이다. 다음의 예제 코드를 보자.

업데이트 전에 사용해야했던 Selector 위젯

/***** 업데이트 전 *****/

// Person객체의 name속성이 변경되었을 때에만 name값을 가져와서 UI를 다시 그리는 코드이다.
Widget build(BuildContext context) {
  return Selector<Person, String>(
    selector: (context, p) => p.name;
    builder: (context, name, child) {
      return Text(name);
    },
  ),
}

업데이트 후, Selctor 위젯을 사용하지 않아 코드가 간결해졌다.

/***** 업데이트 후 *****/

// Person객체의 name속성이 변경되었을 때에만 name값을 가져와서 UI를 다시 그리는 코드이다.
Widget build(BuildContext context) {
  final String name = context.select((Person p) => p.name;
  return Text(name);
}

이렇게 코드가 간결해졌음에도 Selector위젯의 기능과 동일하다. Person 객체의 name 속성이 변경된 경우에만 위젯을 다시 빌드하고, Person 객체의 다른 속성이 변경되면 위젯을 빌드하지 않는다.

select 메서드를 사용할 때, 반환형을 명시할 수도 있다. 아래의 코드를 보자.

String name = context.select<Person>((Person p) => p.name);

String name = context.select<Person, String>((Person p) => p.name);

<Person, String> 이렇게 String으로 반환한다는 것을 명시할 수 있다.


예제 코드

import 'package:flutter/material.dart';
import 'package:flutter_app/main.dart';
import 'package:provider/provider.dart';

class Person with ChangeNotifier {
  String name;
  int age;

  Person({this.name, this.age});

  void increaseAge() {
    age++;
    notifyListeners();
  }

  void changeName() {
    name = 'John';
    notifyListeners();
  }
}

class Countdown {
  static Stream<String> start() async* {
    int i = 10;
    while (i > 0) {
      await Future.delayed(Duration(seconds: 1), () => i--);
      yield i.toString();
    }
    yield 'end';
  }
}

void main() {
  return runApp(
    MultiProvider(
      providers: [
        StreamProvider<String>(
          create: (_) => Countdown.start(),
          initialData: 'Begin countdown...',
          catchError: (_, error) => error.toString(),
        ),
        ChangeNotifierProvider<Person>(
          create: (_) => Person(name: 'Yuna', age: 31),
        ),
      ],
      child: MyApp(),
    ),
  );
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatelessWidget {
  const MyHomePage({Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Context extensions')),
      body: Padding(
        padding: const EdgeInsets.all(8.0),
        child: Center(
          child: Column(
            children: <Widget>[
              Text('Name: ${Provider.of<Person>(context).name}'),
              Text('context.select: ${context.select((Person p) => p.age)}'),
              Text('context.watch: ${context.watch<String>()}'),
            ],
          ),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          context.read<Person>().increaseAge();
        },
      ),
    );
  }
}

UI를 그리는데 read 메소드를 호출하면 에러가 발생한다. 그래서 UI를 다시 그릴때는 watch 메서드를 사용하면 된다. 


flutterbyexample.com/lesson/using-context-extensions-for-more-control

 

Using context extensions for more control

As of version 4.1.0 of `Provider`, there are some tasty updates to reduce the boiler plate needed to use `Selector` and `Consumer`. (This is impressive, because Provider was already reducing a ton of InheritedWidget boiler plate.) Before examples, it's imp

flutterbyexample.com

Comments