새로운 강의는 이제 https://memi.dev 에서 진행합니다.
memi가 Vue & Firebase로 직접 만든 새로운 사이트를 소개합니다.

바로가기


Flutter와 Firebase로 Android iOS 둘 다 만들기 18 로그인 화면 꾸미기

2 분 소요

이메일로그인을 구현하기 전에 모양을 간단히 꾸며봅니다.

개요

앱을 제작하며 의외로 플러터에서 가장 어려운 것이 화면 구성이었습니다.

별 것 아닐 것 같은데 막상 해보면 화면 밖으로 나가기도하고 스크롤 에러나고 하기 때문에.. 매우 어렵습니다.

물론 초기 개발때 괴로운 부분이며 익숙해지면 그럭저럭 할만합니다.

미루지말고 바로 화면구성을 해보면서 익숙해지면됩니다.

방법은 다양하지만 간단하게 꼬지 않고 만들어보겠습니다.

위젯 쪼개기

작업하다보면 귀찮아서 마구잡이로 탭이 늘어가는 형태가 만들어지는데 나중에 관리가 안됩니다.

확실히 파이썬이 엔드브라켓을 빼고 과감하게 탭 파싱을 한것은 잘한 짓 같습니다.

eg) 리스트형 쪼개기 예

Widget _buildSubItem()
Widget _buildItem()
Widget _buildList()
Widget _buildBody()

@override
Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(title: Text('SignInPage'), ),
    body: _buildBody()
  );
}

스크롤 되게 만들기

자주하는 실수 인데 적당한 높이를 가지고 있을 때 대충 Container에 이것저것 넣고 돌려보면 작은 화면 기기에서 에러가 납니다.

플러터에 뭔가를 표시할 때 늘 생각해야할 것이 크기입니다.

SingleChildScrollView로 감싸서 혹시나 길어지는 컨텐츠도 안전하게 보여집니다.

return SingleChildScrollView(
  child: Text('good')
);

여백 주기

컨테이너를 생성해서 패딩을 줍니다.

return SingleChildScrollView(
  child: Container(
    padding: EdgeInsets.all(10),
    child: Text('good'),
  ),
);

폼 감싸기

final _formKey = GlobalKey<FormState>();

return SingleChildScrollView(
  child: Container(
    padding: EdgeInsets.all(10),
    child: Form(
      key: _formKey,
      child: Column(
        children: <Widget>[
          Text('id'),
          Text('pwd')
        ],
      ),
    ),
  ),
);

폼을 사용하는 목적중 하나가 유효성 판단이기 때문에 키를 지정해줍니다.

입력부 모양 내기

final TextEditingController emailController = TextEditingController();
final TextEditingController passwordController = TextEditingController();
TextFormField(
  decoration: InputDecoration(
    labelText: 'Email',
    hintText: 'eg) johndoe@abc.com',
    border: OutlineInputBorder()
  ),
  controller: emailController,
  keyboardType: TextInputType.emailAddress,
),
TextFormField(
  decoration: InputDecoration(
    labelText: 'Password',
    hintText: 'eg) very difficult text',
    border: OutlineInputBorder()
  ),
  controller: passwordController,
  obscureText: true,
),

decoration을 통해 다양한 모양을 낼 수 있는데 제가 좋아하는 스타일로 만들어봤습니다.

간격 띄우기

가끔 위젯끼리 달라붙어 있을 때 적당하게 띄우는 것이 필요한데 의외로 혼란스럽습니다.

Padding(padding: EdgeInsets.all(10),),
Container(height: 10,),
SizedBox(height: 10,),

방법이 너무 다양해서 문제인데.. 가장 간단한 SizedBox가 용도에 맞을 것 같습니다.

로그인 버튼 만들기

어느정도 데코가 들어간 버튼 만들기가 생각보다 어렵습니다.

만약 아이콘 버튼을 만든다면..

RaisedButton(         
  color: Theme.of(context).primaryColor,
  textColor: Colors.white,          
  child: Row(
    mainAxisAlignment: MainAxisAlignment.spaceAround,
    children: <Widget>[
      Icon(Icons.email),
      Text('Sign in with Email')
    ],
  ),
),

대략 이런 느낌으로 만들어야됩니다. 주렁주렁 데코를 하다보면 시간이 꽤나 갑니다.

그래서 pub.dev의 모듈을 써서 꾸며봤습니다.

참고: https://pub.dev/packages/flutter_signin_button

import 'package:flutter_signin_button/flutter_signin_button.dart';

SignInButton(Buttons.Email, onPressed: () {}),
SignInButton(Buttons.Google, onPressed: () {}),

전체 코드

import 'package:flutter/material.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:google_sign_in/google_sign_in.dart';
import 'package:flutter_signin_button/flutter_signin_button.dart';

class SignInPage extends StatefulWidget {
  SignInPage({Key key}) : super(key: key);
  static const routeName = '/signin';

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

class _SignInPageState extends State<SignInPage> {
  bool _loading = false;
  final _formKey = GlobalKey<FormState>();
  final TextEditingController emailController = TextEditingController();
  final TextEditingController passwordController = TextEditingController();

  _googleSignIn () async {
    final bool isSignedIn = await GoogleSignIn().isSignedIn();
    GoogleSignInAccount googleUser;
    if (isSignedIn) googleUser = await GoogleSignIn().signInSilently();
    else googleUser = await GoogleSignIn().signIn();
    final GoogleSignInAuthentication googleAuth = await googleUser.authentication;

    final AuthCredential credential = GoogleAuthProvider.getCredential(
      accessToken: googleAuth.accessToken,
      idToken: googleAuth.idToken,
    );

    final FirebaseUser user = (await FirebaseAuth.instance.signInWithCredential(credential)).user;
    // print("signed in " + user.displayName);
    return user;
  }

  _buildLoading() {
    return Center(child: CircularProgressIndicator(),);
  }

  _buildBody() {
    return SingleChildScrollView(
      child: Container(
        padding: EdgeInsets.all(10),
        child: Form(
          key: _formKey,
          child: Column(
            children: <Widget>[
              TextFormField(
                decoration: InputDecoration(
                  labelText: 'Email',
                  hintText: 'eg) johndoe@xxx.com',
                  border: OutlineInputBorder(),
                ),
                controller: emailController,
                keyboardType: TextInputType.emailAddress,
              ),
              // Container(height: 10,),
              SizedBox(height: 10,),
              TextFormField(
                decoration: InputDecoration(
                  labelText: 'Password',
                  hintText: 'eg) very hard key',
                  border: OutlineInputBorder(),
                ),
                controller: passwordController,
                obscureText: true,
              ),
              SizedBox(height: 10,),
              SignInButton(
                Buttons.Email,
                onPressed: () {},
              ),
              Text('or'),
              SignInButton(
                Buttons.Google,
                onPressed: () async {
                  setState(() => _loading = true);
                  await _googleSignIn();       
                  setState(() => _loading = false);
                  // Navigator.pushReplacementNamed(context, '/');
                  // Navigator.pushReplacementNamed(context, '/auth');
                  Navigator.pushReplacementNamed(context, '/home');
                },
              ),
              SizedBox(height: 20,),
              Text("Don't have an account yet?"),
              FlatButton(
                child: Text('Sign up'),
                onPressed: () {
                  Navigator.pushNamed(context, '/signup');
                },
              )
            ],
          ),
        ),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Sign in'), ),
      body: _loading ? _buildLoading() : _buildBody()
    );
  }
}

alt iphone

소스

https://github.com/fkkmemi/ff2

영상

댓글남기기