Flutter SnackBar 예제

|

Flutter SnackBar

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'SnackBar Example',
      home: Scaffold(
        appBar: AppBar(
          title: Text('SnackBar Example'),
        ),
        body: SnackBarPage(),
      ),
    );
  }
}

class SnackBarPage extends StatelessWidget {
  final sb = SnackBar(
    content: Text('Hello, SnowDeer ~!!'),
    action: SnackBarAction(
      label: 'ok',
      onPressed: () {},
    ),
  );

  @override
  Widget build(BuildContext context) {
    return Center(
      child: RaisedButton(
        child: Text('SnackBar'),
        onPressed: () {
          Scaffold.of(context).showSnackBar(sb);
        },
      ),
    );
  }
}

Flutter Gallery

|

Flutter Gallery에 가면 많은 Flutter Widget들의 모습과 예제들, 코드 등을 얻을 수 있습니다.

  • GitHub: https://github.com/flutter/flutter/tree/master/dev/integration_tests/flutter_gallery
  • 샘플 예제: https://flutter.github.io/samples/#
  • 공식 레퍼런스: https://flutter.dev/docs

Flutter Drawer 예제

|

Flutter Drawer

Flutter에서 Drawer 사용은 아주 간단합니다. 거의 기본적으로 쓰는 위젯은 Scaffold 위젯에 drawer 속성을 갖고 있습니다.

따라서 다음 코드처럼만 작성해도 Drawer를 사용할 수 있습니다.

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Drawer Example',
      home: Scaffold(
        appBar: AppBar(
          title: Text('Drawer Widget'),
        ),
        drawer: Drawer(),
      ),
    );
  }
}


하지만, 일반적인 Drawer라고 하면 Header 부분도 있어야 하고, 리스트도 있어야 하니깐 다음과 같이 꾸며줄 수 있습니다.

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  final appTitle = 'Drawer Example';

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: appTitle,
      home: MyHomePage(title: appTitle),
    );
  }
}

class MyHomePage extends StatelessWidget {
  final String title;

  MyHomePage({Key key, this.title}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text(title)),
      body: Center(child: Text('Drawer Example')),
      drawer: Drawer(
        // Add a ListView to the drawer. This ensures the user can scroll
        // through the options in the drawer if there isn't enough vertical
        // space to fit everything.
        child: ListView(
          // Important: Remove any padding from the ListView.
          padding: EdgeInsets.zero,
          children: [
            DrawerHeader(
              child: Text('Header'),
              decoration: BoxDecoration(
                color: Colors.blue,
              ),
            ),
            ListTile(
              title: Text('Item 1'),
              onTap: () {
                // Update the state of the app
                // ...
                // Then close the drawer
                Navigator.pop(context);
              },
            ),
            ListTile(
              title: Text('Item 2'),
              onTap: () {
                // Update the state of the app
                // ...
                // Then close the drawer
                Navigator.pop(context);
              },
            ),
          ],
        ),
      ),
    );
  }
}

보다 자세한 내용은 여기를 참고하세요.

Flutter Provider 패턴

|

Provider Pattern

여기서 Provider는 생산자/소비자 패턴에서의 생산자를 의미합니다. ReactRedux와 비슷한 개념이며 Flutter 기본 샘플 프로그램인 카운터 프로그램에 Provider 패턴을 적용해보도록 하겠습니다.


원래 코드

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Provider Pattern Example',
      theme: ThemeData(
        primarySwatch: Colors.indigo,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: MyHomePage(title: 'Provider Pattern Example'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headline4,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );
  }
}


Provider 패턴 적용 맛보기

pubspec.yaml

dependencies:
  flutter:
    sdk: flutter
  provider: ^4.1.3


counter.dart 파일을 다음과 같이 작성합니다.

counter.dart

import 'package:flutter/material.dart';

class Counter with ChangeNotifier {
  int _count = 0;

  int get getCount => _count;

  void incrementCounter() {
    _count = _count + 1;
    notifyListeners();
  }
}


main.dart

여기서 눈여겨 볼 부분은 MyApp 위젯에서 ChangeNotifierProivder를 사용한 것과 _MyHomePageState 클래스에서 Provider를 연동한 부분입니다.

그리고 MyHomePage 위젯이 StatefulWidget에서 StatelessWidget가 되었습니다. 더 이상 _count라는 변수를 가질 필요가 없기 때문에 State를 가질 필요가 없어졌습니다. 기존에는 화면을 갱신하기 위해서 setState() 메소드를 호출했었지만, Provider를 사용하면 notifyListeners() 메소드를 통해 해당 데이터를 사용하는 모든 위젯들을 갱신할 수 있습니다.

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

import 'counter.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Provider Pattern Example',
      theme: ThemeData(
        primarySwatch: Colors.indigo,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: ChangeNotifierProvider<Counter>(
        builder: (context) => Counter(),
        child: MyHomePage(title: 'Provider Pattern Example'),
      ),
    );
  }
}

class MyHomePage extends StatelessWidget {
  final String title;

  MyHomePage({this.title});

  @override
  Widget build(BuildContext context) {
    final consumer = Provider.of<Counter>(context);

    return Scaffold(
      appBar: AppBar(
        title: Text(title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '${consumer.getCount.toString()}',
              style: Theme.of(context).textTheme.headline4,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          Provider.of<Counter>(context, listen: false).increment();
        },
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );
  }
}


Provider 패턴의 장점

  • Provider 패턴은 데이터 공유와 로직의 분리를 좀 더 쉽게 할 수 있게 해줍니다. 로직이 분리되면 코드가 좀 더 깔끔해줍니다.
  • Provider를 이용하면 각 위젯을 StatefulWidget 대신 StatelessWidget로 변경할 수 있어서 위젯의 부담이 줄어듭니다. 각 위젯이 상태값을 가지지 않는 다는 것은 그만큼 함수형 구현(동일한 입력값에 대해서는 항상 동일한 결과가 출력됨)에 더 근접하는 것이며, 보다 더 안정적인 코드를 작성할 수 있습니다.
  • 앱을 구성하는 여러 화면에서 참조해야 하는 공통의 데이터를 관리할 때 유용합니다.


Provider 패턴의 구조

위에서 작성했던 예제를 조금 더 구조화해서 설명하도록 하겠습니다.

Provider는 기본적으로 child로 위젯(Widget)을 가집니다. 위에서 작성했던 코드에는 다음과 같은 형태로 사용하고 있습니다.

ChangeNotifierProvider<Counter>(
  builder: (context) => Counter(),
  child: MyHomePage(title: 'Provider Pattern Example'),
)

그리고 공유하는 데이터는 ChangeNotifierwith로 구현해줍니다. notifyListeners() 메소드를 통해 해당 데이터를 사용하는 위젯들에게 변경 이벤트를 전송해줍니다.

import 'package:flutter/material.dart';

class Counter with ChangeNotifier {
  int _count = 0;

  int get getCount => _count;

  void incrementCounter() {
    _count = _count + 1;
    notifyListeners();
  }
}

위의 예제에서는 여러 개의 Provider를 사용했지만, 아래의 예제처럼 여러 개의 Proivder를 사용할 수도 있습니다. (위의 예제보다는 좀 더 정돈된 코드입니다.)

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MultiProvider(
      providers: [
        ChangeNotifierProvider(
          create: (context) => Counter(),
        ),
      ],
      child: MaterialApp(
        title: 'Provider Pattern Example',
        theme: ThemeData(
          primarySwatch: Colors.indigo,
          visualDensity: VisualDensity.adaptivePlatformDensity,
        ),
        home: MyHomePage(title: 'Provider Pattern Example'),
      ),
    );
  }
}

Provider의 데이터는 아래와 같이 Provider.of를 이용해서 가져올 수 있습니다.

final consumer = Provider.of<Counter>(context);

다만, 위의 코드처럼 구현하면 위젯의 재빌드가 자주 발생하게 되는데 이 경우는 Provider.of 대신 ConsumerSelector를 사용하고 해당 데이터를 사용하는 부분으로 호출 코드를 이동시켜주면 됩니다.

Consumer<Counter>(
  builder: (context, counter, child) {
    return Text(
      '${counter.getCount.toString()}',
      style: Theme.of(context).textTheme.headline4,
    );
  },
),

전체 위젯 코드는 다음과 같습니다.

class MyHomePage extends StatelessWidget {
  final String title;

  MyHomePage({this.title});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(
              'You have pushed the button this many times:',
            ),

            Consumer<Counter>(
              builder: (context, counter, child) {
                return Text(
                  '${counter.getCount.toString()}',
                  style: Theme.of(context).textTheme.headline4,
                );
              },
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          Provider.of<Counter>(context, listen: false).increment();
        },
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );
  }
}

React CORS 문제 해결하기(Proxy 이용)

|

CORS

CORS(Cross-Origin Resource Sharing)는 교차 출처 리소스 공유라는 기능으로 실행 중인 웹 어플리케이션이 다른 출처의 리소스에 접근할 수 있는 권한을 부여할 수 있도록 웹브라우저에 알려주는 기능입니다.

React로 개발을 하다 다른 서버에 있는 데이터를 가져올 때 다음과 같은 오류가 발생하는 경우가 있습니다.

Access to XMLHttpRequest at 'http://xxx' from origin 'http://localhost:3000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.


이 경우 해당 컨텐츠를 제공하는 서버쪽에서 CORS 설정을 헤더(header)에 실어서 보내주는 방법이 정석적인 방식이지만 제3자가 다른 웹사이트의 데이터를 가져오는 경우에는 쉽지 않습니다.

Proxy 역할을 해주는 중간 서버를 만들어서 문제를 해결할 수도 있지만 여전히 번거롭습니다. 그런데 Webpack에서 간단한 방법으로 Proxy 기능을 지원해주기 때문에 package.json 파일에 다음 항목만 추가해주면 간단하게 CORS 문제를 해결할 수 있습니다.


package.json

{
  "proxy": "http://xxx"
}

위에서 http://xxx는 실제 접속하고자하는 서버의 루트 URL 입니다.


그 이후 실제 http 리퀘스트를 전송하는 코드에서는 위에서 선언한 루트 URL을 뺀 나머지 부분을 요청하면 됩니다.


예제

package.json에 다음과 같이 입력합니다.

{
  "proxy": "https://snowdeer.com"
}

그리고 원래 http 리퀘스트 코드가 다음과 같다면

import React, { useState, useEffect } from 'react';
import './App.css';
import axios from 'axios'

import WellstoryMenuApp from './component/WellstoryMenuApp'

const App = () => {
  const URL = 'https://snowdeer.com/menu/getMenuList.do?type=2'

  const [data, setData] = useState(null)
  const [loading, setLoading] = useState(false)

  useEffect(() => {
    const fetchData = async () => {
      setLoading(true)

      try {
        const response = await axios.get(URL, )
        console.log(response)
      }
      catch (e) {
        console.log(e)
      }
      setLoading(false)
    }

    fetchData()
  }, [])

  return (
    <div>
      Hello
    </div>
  )

}

export default App;


여기서 URL을 다음과 같이 수정하면 됩니다.

const App = () => {
  const URL = '/menu/getMenuList.do?type=2'
}