Flutter 단기집중과정 - 2단계

정적 UI를 만드는 방법을 보여주었습니다. 이제 우리는 그것을 동적으로 만들 것입니다.

이전 예제에서 버튼을 활성화하기 전에 앱이 API와 통신해야 하기 때문에 시작 시 버튼을 비활성화하고 싶다고 가정해 보겠습니다. 이를 달성하려면 몇 가지 단계가 필요합니다.

1/Button Widget의 활성화 상태는 onPressed 속성에 설정된 작업이 있는지 여부에 따라 결정됩니다. 현재 한 세트가 있습니다.

            RaisedButton(
              child: Text("A Button"),
              onPressed: () => print("Pressed"),
            ),


변경해 보겠습니다.

            RaisedButton(
              child: Text("A Button"),
              onPressed: buttonEnabled ? () => print("Pressed") : null,
            ),

buttonEnabled는 현재 존재하지 않는 변수입니다. 그럼 이것을 어떻게 전달할까요? Builder 클래스(유형이 아닌 개념)를 통해, 이 경우 a FutureBuilder .

2/RaisedButton를 이 다른 FutureBuilder Widget 안에 감쌉니다.

            FutureBuilder<bool>(
              future: null,
              builder: (context, AsyncSnapshot<bool> snapshot) {
                final buttonEnabled = snapshot.hasData && snapshot.data;
                return RaisedButton(
                  child: Text("A Button"),
                  onPressed: buttonEnabled ? () => print("Pressed") : null,
                );
              }
            ),


아직 Future<bool>가 없으므로 future: null는 비워 두었지만 나머지는 설명하겠습니다. UI를 구축하기 위해 FutureBuilderbuilder 함수는 future가 완료되기 전에 (최소한) 한 번 호출되고 완료되면 호출됩니다. 완료 여부와 완료된 경우 결과가 스냅샷으로 전달됩니다. 이 스냅샷을 기반으로 버튼을 활성화하거나 비활성화합니다.

빌더를 생각하는 좋은 방법은 다음과 같습니다.

표시할 위젯이나 위젯에 설정할 속성을 결정하려면 약간의 로직이 필요합니다. 따라서 조건을 넣을 빌더가 필요합니다.

또는 더 간단하게:

if 문이 필요하므로 빌더가 필요합니다.

이제 Future가 들을 수 있는 FutureBuilder를 만들겠습니다.

3/이렇게 하려면 Widget를 상태가 있는 것으로 변경해야 합니다. 현재 상태에 대해 알 필요가 없는 StatelessWidget 를 위한 Widget 입니다. Future 를 도입하고 있고 Future 가 한 번만 실행되기를 원하므로 Widget 가 상태를 가져야 합니다.

Android Studio의 alt+enter를 통해 MyApp가 아닌 확장StatefulWidget으로 StatelessWidget를 변경합니다.



예리한 눈을 가진 사람은 텍스트 측면에서 크게 변경된 사항이 없음을 알 수 있습니다. 우리의 build 함수는 새로운 내부 클래스로 이동했고 MyApp는 이제 이 내부 클래스의 인스턴스를 생성합니다.

4/이제 Future_MyAppState의 속성으로 추가하고 _MyAppStateinitState 메서드에서 생성해 보겠습니다.

class _MyAppState extends State<MyApp> {
  Future<bool> future;

  @override
  void initState() {
    super.initState();
    future = Future.delayed(Duration(seconds: 2), () => true);
  }

  @override
  Widget build(BuildContext context) {


5/이제 FutureBuilder에 연결합니다.

              FutureBuilder<bool>(
                future: future,
                builder: (context, AsyncSnapshot<bool> snapshot) {


앱 실행:



다른 빌더


FutureBuilder 처럼 자주 사용되는 것처럼 StreamBuilder 가 있습니다(실제로 제 경우에는 훨씬 더 자주 사용됨). 이는 여러 번 변경될 수 있는 데이터 스트림을 수신한다는 점을 제외하면 매우 유사합니다. 예를 들어 진행률 표시기 또는 목록 내용의 가시성.

좋은 방법은 화면의 모든 상태를 데이터 클래스에 유지하고 비즈니스 로직이 이 상태를 업데이트하고 스트림을 통해 업데이트를 전송하도록 하는 것입니다. UI( Widget s)는 StreamBuilder 를 사용하여 이를 수신하고 이 상태에 있는 모든 것을 반영하도록 UI를 업데이트할 수 있습니다.

이 예에서는 다음과 같이 Android Studio의 기본 Flutter 프로젝트를 조정하여 스트림을 사용하고 버튼을 누를 때마다 비동기 코드를 수행합니다.



상태를 유지하는 간단한 클래스를 정의해 보겠습니다.

class MyHomePageState {
  final int counter;
  final bool showSpinner;

  MyHomePageState(this.counter, this.showSpinner);
}


이제 비즈니스 로직을 수행하려면 일반 Dart 클래스(즉, Flutter 코드가 아님)가 필요합니다.

class Logic {
  // private stream controller that will manage our stream
  final _streamController = StreamController<MyHomePageState>();

  // expose the stream publicly
  Stream<MyHomePageState> get homePageState => _streamController.stream;

  MyHomePageState _currentState;

  Logic() {
    _updateState(MyHomePageState(0, false));
  }

  // update the state and broadcast it via the stream controller
  void _updateState(MyHomePageState newState) {
    _currentState = newState;
    _streamController.sink.add(_currentState);
  }

  // increment the counter after waiting 1 second, to simulate a network call for example.
  Future<void> incrementCounter() async {
    _updateState(MyHomePageState(_currentState.counter, true));
    await Future.delayed(Duration(seconds: 1));
    _updateState(MyHomePageState(_currentState.counter + 1, false));
  }
}


이제 UI는 바보처럼 작동하고 이벤트를 보내고(버튼 탭) 표시할 항목(스트림의 콘텐츠)을 수신하여 이 클래스Logic를 사용합니다.

class MyHomePage extends StatelessWidget {
  final Logic logic = Logic();

  Widget build(BuildContext context) {
    return StreamBuilder<MyHomePageState>(
      stream: logic.homePageState,
      builder: (context, AsyncSnapshot<MyHomePageState> snapshot) {
        print("snapshot: ${snapshot?.data ?? "null"} ");
        final asyncInProgress = snapshot.data?.showSpinner ?? true;
        return Scaffold(
          appBar: AppBar(title: Text("Stream Example")),
          body: Center(
            child: asyncInProgress
                ? CircularProgressIndicator()
                : Column(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: <Widget>[
                      Text('You have pushed the button this many times:'),
                      Text(
                        '${snapshot.data.counter}',
                        style: Theme.of(context).textTheme.headline4,
                      ),
                    ],
                  ),
          ),
          floatingActionButton: FloatingActionButton(
            onPressed: asyncInProgress ? null : logic.incrementCounter,
            tooltip: 'Increment',
            child: Icon(Icons.add),
          ),
        );
      },
    );
  }
}


플로팅 작업 버튼은 진행률 스피너가 표시되지 않는 경우에만 활성화되므로 비동기 작업이 완료될 때까지 다시 누를 수 없습니다.

이 짧은 시리즈의 마지막 부분에서는 모든 앱에 적용할 수 있는 간단한 패턴인 탐색을 다룹니다.

좋은 웹페이지 즐겨찾기