MacOS에서 Sandbox Permission 추가하기

|

MacOS 프로그램들은 기본적으로 Sandbox에서 동작하고 있습니다. 따라서 네트워크나 공유 자원 등에 접근할 때 Permission을 획득해야 사용할 수 있는 경우가 있습니다.

대표적인 예로 이미지를 네트워크를 통해 가져오는 Image.network 함수를 사용할 경우 다음 오류가 발생합니다.

SocketException: Connection failed (OS Error: Operation not permitted, errno = 1)

이 경우 macos/Runner/DebugProfile.entitlements 파일에 다음 권한을 추가해주면 됩니다.

    <key>com.apple.security.network.client</key>
    <true/>


MacOS의 Permission 리스트

MacOS에서 요구하는 Permission 리스트는 여기에서 확인하실 수 있습니다.

또한 런타임 중에 확인해야 하는 Hardened Runtime 리스트는 여기에서 확인 가능합니다.

Flutter BottomNavigationBar 예제

|

하단 탭을 이용한 네비게이션 예제 코드입니다.

main.dart

import 'package:flutter/material.dart';

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

class SnowDeerApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'SnowDeer App',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        primaryColor: Colors.deepPurple,
      ),
      home: MainWidget(),
    );
  }
}

class MainWidget extends StatefulWidget {
  @override
  State createState() => _MainWidgetState();
}

class _MainWidgetState extends State<MainWidget> {
  var _index = 0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Hello Snowdeer'),
      ),
      body: Center(
        child: Text('Page Index: $_index'),
      ),
      bottomNavigationBar: BottomNavigationBar(
          currentIndex: _index,
          onTap: (index) {
            setState(() {
              _index = index;
            });
          },
          items: [
            BottomNavigationBarItem(
              title: Text('Home'),
              icon: Icon(Icons.home),
            ),
            BottomNavigationBarItem(
              title: Text('DoAction'),
              icon: Icon(Icons.email),
            ),
            BottomNavigationBarItem(
              title: Text('DoBehavior'),
              icon: Icon(Icons.favorite),
            ),
          ]),
    );
  }
}


실제 페이지 이동하는 예제

import 'package:flutter/material.dart';

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

class SnowDeerApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'SnowDeer App',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        primaryColor: Colors.deepPurple,
      ),
      home: MainWidget(),
    );
  }
}

class MainWidget extends StatefulWidget {
  @override
  State createState() => _MainWidgetState();
}

class _MainWidgetState extends State<MainWidget> {
  var _index = 0;
  var _pages = [
    HomePageWidget(),
    DoActionPageWidget(),
    DoBehaviorPageWidget(),
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Hello Snowdeer'),
        actions: [
          IconButton(
            icon: Icon(
              Icons.add,
              color: Colors.white,
            ),
            onPressed: () {},
          )
        ],
      ),
      body: _pages[_index],
      bottomNavigationBar: BottomNavigationBar(
          currentIndex: _index,
          onTap: (index) {
            setState(() {
              _index = index;
            });
          },
          items: [
            BottomNavigationBarItem(
              title: Text('Home'),
              icon: Icon(Icons.home),
            ),
            BottomNavigationBarItem(
              title: Text('DoAction'),
              icon: Icon(Icons.email),
            ),
            BottomNavigationBarItem(
              title: Text('DoBehavior'),
              icon: Icon(Icons.favorite),
            ),
          ]),
    );
  }
}

class HomePageWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Text('Home'),
    );
  }
}

class DoActionPageWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Text('DoAction'),
    );
  }
}

class DoBehaviorPageWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Text('DoBehavior'),
    );
  }
}

Flutter 화면 전환(복수 파일 이용)

|

Flutter에서의 화면 전환 예제를 여러 개의 파일로 나누어서 호출하도록 변경했습니다. 다른 파일을 가져올 때 다음과 같이 두 가지 방식으로 import 할 수 있습니다.

import 'first_screen_widget.dart';
import 'package:snowdeer_hello_flutter/second_screen_widget.dart';


main.dart

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

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

class SnowDeerApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'SnowDeer App',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        primaryColor: Colors.deepPurple,
      ),
      home: FirstScreenWidget(),
    );
  }
}


first_screen_widget.dart

import 'package:flutter/material.dart';
import 'package:snowdeer_hello_flutter/second_screen_widget.dart';

class FirstScreenWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('First Screen'),
      ),
      body: Container(
          width: double.infinity,
          height: double.infinity,
          color: Colors.amber,
          child: Center(
              child: RaisedButton(
            child: Text('Move to Second Screen'),
            onPressed: () {
              Navigator.push(
                context,
                MaterialPageRoute(builder: (context) => SecondScreenWidget()),
              );
            },
          ))),
    );
  }
}


second_screen_widget.dart

import 'package:flutter/material.dart';

class SecondScreenWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Second Screen'),
      ),
      body: Container(
          width: double.infinity,
          height: double.infinity,
          color: Colors.green,
          child: Center(
              child: RaisedButton(
              child: Text('Back to First Screen'),
              onPressed: () {
                Navigator.pop(context);
              },
          ))),
    );
  }
}

Flutter 화면 전환

|

Flutter에서 화면 전환을 하는 예제입니다. Navigator를 이용해서 새로운 화면을 push 또는 pop 하여 화면간 전환을 합니다.

main.dart

import 'package:flutter/material.dart';

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

class SnowDeerApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'SnowDeer App',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        primaryColor: Colors.deepPurple,
      ),
      home: FirstScreenWidget(),
    );
  }
}

class FirstScreenWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('First Screen'),
      ),
      body: Container(
          width: double.infinity,
          height: double.infinity,
          color: Colors.amber,
          child: Center(
              child: RaisedButton(
            child: Text('Move to Second Screen'),
            onPressed: () {
              Navigator.push(
                context,
                MaterialPageRoute(builder: (context) => SecondScreenWidget()),
              );
            },
          ))),
    );
  }
}

class SecondScreenWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Second Screen'),
      ),
      body: Container(
          width: double.infinity,
          height: double.infinity,
          color: Colors.green,
          child: Center(
              child: RaisedButton(
            child: Text('Back to First Screen'),
            onPressed: () {
              Navigator.pop(context);
            },
          ))),
    );
  }
}


화면간 데이터 전달하는 방법

아래와 같이 사용자 정의 클래스를 만들고 해당 인스턴스를 다음 화면에 전달하는 예제입니다.

class Item {
  int id;
  String name;

  Item({this.id, this.name});
}


전체 코드

import 'package:flutter/material.dart';

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

class SnowDeerApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'SnowDeer App',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        primaryColor: Colors.deepPurple,
      ),
      home: FirstScreenWidget(),
    );
  }
}

class Item {
  int id;
  String name;

  Item({this.id, this.name});
}

class FirstScreenWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('First Screen'),
      ),
      body: Container(
          width: double.infinity,
          height: double.infinity,
          color: Colors.amber,
          child: Center(
              child: RaisedButton(
            child: Text('Move to Second Screen'),
            onPressed: () {
              final item = Item(id: 10, name: 'snowdeer');
              Navigator.push(
                context,
                MaterialPageRoute(
                    builder: (context) => SecondScreenWidget(item: item)),
              );
            },
          ))),
    );
  }
}

class SecondScreenWidget extends StatelessWidget {
  final Item item;

  SecondScreenWidget({this.item});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Second Screen"),
      ),
      body: Container(
          width: double.infinity,
          height: double.infinity,
          color: Colors.green,
          child: Center(
            child: Text('id: ${item.id}, name: ${item.name}'),
          )),
    );
  }
}

위에서 두 번째 위젯인 SecondScreenWidget의 생성자를 통해서 item 인스턴스를 속성에 넣어주도록 했습니다.

생성자는 아래와 같이 필수 입력 매개변수로 선언하면 좀 더 안전한 코드가 됩니다.

SecondScreenWidget({@required this.item});

Flutter Widget을 이용해 로그인 화면 배치해보기

|

로그인 화면 배치

Image

main.dart

import 'package:flutter/material.dart';

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

class SnowDeerExample extends StatelessWidget {
  static const title = 'SnowDeer\'s Login Example';

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        title: title,
        debugShowCheckedModeBanner: false,
        home: Scaffold(
//            appBar: AppBar(title: Text(title)),
            body: Container(
              margin: EdgeInsets.symmetric(horizontal: 40),
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                crossAxisAlignment: CrossAxisAlignment.center,
                children: [
                  Container(
                    child: Image.asset(
                      'assets/images/snowdeer.png',
                      width: 160,
                      height: 160,
                    ),
                  ),
                  Container(
                      margin: EdgeInsets.only(top: 20),
                      child: TextFormField(
                        keyboardType: TextInputType.emailAddress,
                        initialValue: 'snowdeer0314@gmail.com',
                        decoration: InputDecoration(
                          border: OutlineInputBorder(),
                        ),
                      )),
                  Container(
                      margin: EdgeInsets.only(top: 8),
                      child: TextFormField(
                        obscureText: true,
                        decoration: InputDecoration(
                          border: OutlineInputBorder(),
                          hintText: 'Input password',
                        ),
                      )),
                  Row(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: [
                      RaisedButton(
                        child: Text("Login"),
                        onPressed: () => {print("LoginButton is Clicked.")},
                      ),
                      SizedBox(
                        width: 8,
                      ),
                      RaisedButton(
                        child: Text("Cancel"),
                        onPressed: () => {print("CancelButton is Clicked.")},
                      )
                    ],
                  )
                ],
              ),
            )));
  }
}