brunch

[번역]Flutter+Firebase noteapp 1

Flutter 와 Firebase 을 이용한 간단 Note App 만들기

by 이종우 Peter Lee

https://firebase.google.com/docs/auth/android/google-signin#before_you_begin

https://en.wikipedia.org/wiki/Reactive_programming

root gatekeeper widget

https://firebase.google.com/docs/firestore

원본 : https://medium.com/flutter-community/build-a-note-taking-app-with-flutter-firebase-part-i-53816e7a3788


Flutter + Firebase를 사용하여 메모 작성 앱 구축 — 1 부


Google Keep의 단순화 된 '복제본'을 처음부터 만드는 방법으로 해보겠습니다.

1*6HTi-RPUL_cmp-UDURxpAg.gif Flutter Keep


저는 https://www.google.com/keep 의 팬입니다 . 출시 된 이래로 계속 사용하고 있습니다. 보류중인 작업, 집안일에 대한 알림, 거의 기억해야 할 모든 내용을 Keep에 넣었습니다. 사용이 직관적이며 우선 순위에 집중할 수 있도록 도와줍니다.지난 2 년 동안 https://flutter.dev/ Flutter 앱을 개발해 왔으므로 Keep from scratch와 같은 노트북 앱을 만드는 것이 흥미로울 것 같습니다.


내가 지금까지 만든 소위 ' Flutter Keep ' 앱은 다음과 같습니다.

https://youtu.be/GXNXodzgbcM

Flutter Keep 앱 데모


이 과정을 일련의 기사로 소개하겠습니다. 반복하는 동안 주요 구성 요소가 앱에 추가되어 이해하기 쉽습니다.


이 시리즈의 첫 번째 부분에서는 Flutter 프로젝트를 설정하고 인증 프로세스를 제공하며 메모 목록을 표시하는 간단한 화면을 제공합니다.


시작해 봅시다!!


프로젝트를 만들기 전에 Android 및 iOS 이외의 웹에서 앱을 실행할 수있게 코멘트를 실행해야 할 수 있습니다.

command : flutter config -- enable-web


이제 Flutter Keep 앱을 만들려면 다음 명령을 실행 하십시오

command : flutter create flt_keep


flt_keep 는 import 문에 사용될 패키지 이름입니다


Flutter를 처음 사용 하는 사람들은 https://flutter.dev/docs/get-started/install 시작 안내서를 따라 SDK를 설치하고 프로젝트 구조에 익숙해 지십시오.


데이터 구조

노트북 앱의 경우 가장 먼저 고려해야 할 것은 메모를 유지하고 쿼리하는 방법입니다.


내가 중점적으로 고려하는 것은 다음과 같습니다.


첫째, 프라이버시. 다른 계정의 메모는 서로 분리되어야합니다.

둘째, 앱이 오프라인에서 작동해야합니다. 사용자는 모든 네트워크 조건에서 메모를 할 수 있어야합니다. 네트워크 연결이 복구 될 때 데이터를 동기화하는 것은 앱의 책임입니다.


내가 선택한 것은 https://firebase.google.com/docs/firestore Cloud Firestore 입니다 . 주로 이전 프로젝트에서 얻은 경험 때문이지만 적응하기도 쉽기 때문입니다.


저는 전용 모음을 사용하여 각 사용자의 메모, 단일 메모에 대한 하나의 문서를 저장하기로 결정했습니다. 그 이유는 다음과 같습니다 :

각 계정 데이터의 더 나은 분리

쉬운 쿼리

데이터 읽기 및 쓰기의 일부 https://firebase.google.com/docs/firestore/quotas#limits 제한을 회피 가능


이 방법은 비용도 함께 제공되지만 시연 목적으로도 허용됩니다. 즉, 각 컬렉션에 대해 인덱스를 동적으로 만들어야하므로 이후 기사에서이 문제에 대해 설명하겠습니다.


현재 데이터 구조는 다음과 같습니다.


1*K11nEEwAPoEJnSexd22dRg.jpeg Firestore data structure


앱 아키텍처

이제 앱 로직을 구성하는 방법을 고려해야합니다. 시연을 위해 앱에 '실제'아키텍처를 적용하는 것은 가치가 없습니다. 그러나 여전히 앱의 여러 화면에서 상태를 관리해야합니다.


이 경우 https://pub.dev/packages/provider 제공자 패키지를 사용하여 앱 상태를 관리합니다. 반응형 (또는 데이터 중심) 스타일로 코드를 작성할 수 있습니다.


앱에서 가장 중요한 화면은 다음과 같습니다.

로그인 상태를 확인하는 인증 화면은 인증 된 사용자만 메모 할 수 있도록 합니다.

최신 메모 상태를 표시하는 메모 목록 화면은 모든 메모 변경에 적절하게 반응해야 합니다.

노트 편집기는 편집중인 특정 노트의 외부 수정에 적절하게 반응해야 합니다.


https://pub.dev/packages/provider 제공자를 통해 보다 깨끗한 코드베이스로 위의 요구 사항을보다 쉽게 충족 할 수 있습니다.


Scheme 를 염두에 두고, 이제 코드 작성을 시작하겠습니다.


우리가 provider 와 Firebase SDK 를 사용하려면, pubspec.yaml 파일에 종속성을 추가해야 해야 합니다.


provider: ^4.0.2
firebase_core: ^0.4.4
firebase_auth: ^0.15.4
cloud_firestore: ^0.13.3
google_sign_in: ^4.1.4


Android 및 iOS 용 Firebase SDK와 웹 플랫폼(https://firebase.google.com/docs/web/setup)을 통합하는 것은 이 지침(https://firebase.google.com/docs/flutter/setup)에 따라 주세요.


앱에서 입력


반드시 기억해야할 것은 인증되지 않은 사용자는 거부 해야합니다. 루트에 gatekeeper 위젯을 작성하십시오.


main.dart


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

/// Root widget of the application.

class NotesApp extends StatelessWidget {

@override

Widget build(BuildContext context) => StreamProvider.value(

initialData: CurrentUser.initial,

value: FirebaseAuth.instance.onAuthStateChanged.map((user) => CurrentUser.create(user)),

child: Consumer<CurrentUser>(

builder: (context, user, _) => MaterialApp(

title: 'Flutter Keep',

home: user.isInitialValue

? Scaffold(body: const Text('Loading...'))

: user.data != null ? HomeScreen() : LoginScreen(),

),

),

);

}


여기서 StreamProvider/ Consumer pair 하는 것은 onAuthStateChanged 을 보기 위함이며,

Firebase 인증 이벤트들의 스트림과 현재의 상태에 따라 통보 및 재 빌드 위젯 얻을 것이다.

Customer 는 알람정보를 얻고, 현재 상태을 적용하는 위젯을 re-Build 하게 됩니ㅏ다.


아래 소스에서 약간의 트릭은 FirebaseUser 을 래핑하기 위해서 초기값과 비인증한 상태를 구분하기 위해서 CusttentUser 을 사용하는데, 모두 Null 으로 만듭니다.


current_user.dart


class CurrentUser {

final bool isInitialValue;

final FirebaseUser data;

const CurrentUser._(this.data, this.isInitialValue);

factory CurrentUser.create(FirebaseUser data) => CurrentUser._(data, false);

/// The inital empty instance.

static const initial = CurrentUser._(null, true);

}


Google 로그인 및 Firebase 인증

통합하기 쉽기 때문에 Google 로그인을 예로 사용합니다. 실제로 Firebase Auth가 지원하는 많은 서비스 중 하나 일뿐입니다. Firebase 콘솔에서 필요한 기능을 활성화 할 수 있습니다.


1*p28rWu_gssWRU4xwb5NTQg.png Available authentication providers


Google 로그인을 사용하여 인증하는 code snippet

: login_screen.dart


class LoginScreen extends StatefulWidget {

@override

State<StatefulWidget> createState() => _LoginScreenState();

}

class _LoginScreenState extends State<LoginScreen> {

final _auth = FirebaseAuth.instance;

final _googleSignIn = GoogleSignIn();

String _errorMessage;

@override

Widget build(BuildContext context) => Scaffold(

body: Center(

child: Column(

children: <Widget>[

RaisedButton(

onPressed: _signInWithGoogle,

child: const Text('Continue with Google'),

),

if (_errorMessage != null) Text(

_errorMessage,

style: const TextStyle(color: Colors.red),

),

],

),

),

);

void _signInWithGoogle() async {

_setLoggingIn(); // show progress

String errMsg;

try {

final googleUser = await _googleSignIn.signIn();

final googleAuth = await googleUser.authentication;

final credential = GoogleAuthProvider.getCredential(

idToken: googleAuth.idToken,

accessToken: googleAuth.accessToken,

);

await _auth.signInWithCredential(credential);

} catch (e) {

errMsg = 'Login failed, please try again later.';

} finally {

_setLoggingIn(false, errMsg); // always stop the progress indicator

}

}

/// update the logging-in indicator, & show error message if any

void _setLoggingIn([bool loggingIn = true, String errMsg]) {

if (mounted) {

setState(() {

_loggingIn = loggingIn;

_errorMessage = errMsg;

});

}

}

}


Google 로그인 자격 증명을 요청한 다음이를 사용하여 Firebase 인증을 받으십시오.


당신은 인증이 완료된 후에는 수행 할 작업이 없음을 알 수 있습니다.

이 상황에서 FirebaseAuth.onAuthStateChanged 스트림은 'Signed-in' 이벤트를 생성하여 root gatekeeper widget 의 재 빌드를 트리거하여 HomeScreen 이 렌더링됩니다.


위의 예제는 https://en.wikipedia.org/wiki/Reactive_programming 리 액티브 프로그래밍의 예제입니다 . 상태를 변경하면 상태에 관심이있는 리스너가 나머지 작업을 수행합니다.


프로젝트로 돌아가서 로그인 화면을 테스트하기 전에 다음 설정을 무시하지 않아야합니다.


Android 플랫폼의 경우 Firebase 콘솔에서 https://firebase.google.com/docs/auth/android/google-signin#before_you_begin SHA-1 fingerprint 지정 해야합니다.

iOS 플랫폼의 경우 Xcode 프로젝트에 https://firebase.google.com/docs/auth/ios/google-signin#2_implement_google_sign-in 사용자 정의 URL sheme 을 추가 해야합니다.

웹 플랫폼의 경우, web/index.html 에 같은 메타 태그를 <meta name="google-signin-client_id" content="{web_client_id}"> 추가해야 합니다. 이를 통해서 당신은 'OAuth 2.0 Client IDs’ https://console.cloud.google.com/apis/credentials 자격 증명 페이지 Google 클라우드 콘솔을 찾을 수 있습니다.


노트 쿼리


인증 된 사용자를 통해 이제 앱의 기본 화면 인 메모 목록을 입력 할 수 있습니다.


그러나 메모 편집기없이 첫 번째 메모를 어떻게 추가 할 수 있습니까? Firebase 콘솔을 사용하여이 작업을 수행 할 수 있습니다.

1*JA5qv8JPFfjNA9zAQ3Nhcg.png Firestore console



notes-{user_id} 처럼 컬렉션 이름을 지정하면 Firebase 콘솔의 인증 페이지에서 사용자 ID를 찾을 수 있습니다


개인 정보 보안을 강화하기 위해 데이터 세트에 대한 액세스 규칙을 설정하여 사용자가 자신의 메모 만보고 편집 할 수 있도록 할 수 있습니다.


1*8bNXjjHd9lQqAE-BKtPEsQ.png Firestore data access rules


Firestore에서 메모를 검색하려면, 개별 메모를 나타내는 모델과 Firestore 모델과 자체 메모를 변환하는 기능이 필요합니다.


note.dart

class Note {

final String id;

String title;

String content;

Color color;

NoteState state;

final DateTime createdAt;

DateTime modifiedAt;

/// Instantiates a [Note].

Note({

this.id,

this.title,

this.content,

this.color,

this.state,

DateTime createdAt,

DateTime modifiedAt,

}) : this.createdAt = createdAt ?? DateTime.now(),

this.modifiedAt = modifiedAt ?? DateTime.now();

/// Transforms the Firestore query [snapshot] into a list of [Note] instances.

static List<Note> fromQuery(QuerySnapshot snapshot) => snapshot != null ? toNotes(snapshot) : [];

}

/// State enum for a note.

enum NoteState {

unspecified,

pinned,

archived,

deleted,

}

/// Transforms the query result into a list of notes.

List<Note> toNotes(QuerySnapshot query) => query.documents

.map((d) => toNote(d))

.where((n) => n != null)

.toList();

/// Transforms a document into a single note.

Note toNote(DocumentSnapshot doc) => doc.exists

? Note(

id: doc.documentID,

title: doc.data['title'],

content: doc.data['content'],

state: NoteState.values[doc.data['state'] ?? 0],

color: _parseColor(doc.data['color']),

createdAt: DateTime.fromMillisecondsSinceEpoch(doc.data['createdAt'] ?? 0),

modifiedAt: DateTime.fromMillisecondsSinceEpoch(doc.data['modifiedAt'] ?? 0),

)

: null;

Color _parseColor(num colorInt) => Color(colorInt ?? 0xFFFFFFFF);


다시 우리는 HomeScreen 에서 StreamProvider을 사용해야 하며, 이를 통해서 어떠한 변화가 백엔드 일어날 것을 알고 바로 여기에 노트 쿼리 결과를 노트에 반영할 수 있도록 계속해서 감시하게 됩니다. Firestore SDK는 또한 필요한 https://firebase.google.com/docs/firestore/manage-data/enable-offline오프라인 기능 을 제공하므로 데이터 액세스에 사용되는 코드를 변경할 필요가 없습니다. 이전에 구축한 gatekeeper widget 덕분에 Provider.of<CurrentUser> 대비해서 언제든지 인증 정보를 검색 할 수 있습니다.


home_screen.dart

/// Home screen, displays [Note] in a Grid or List.

class HomeScreen extends StatefulWidget {

@override

State<StatefulWidget> createState() => _HomeScreenState();

}

class _HomeScreenState extends State<HomeScreen> {

bool _gridView = true; // `true` to show a Grid, otherwise a List.

@override

Widget build(BuildContext context) => StreamProvider.value(

value: _createNoteStream(context),

child: Scaffold(

body: CustomScrollView(

slivers: <Widget>[

_appBar(context), // a floating appbar

const SliverToBoxAdapter(

child: SizedBox(height: 24), // top spacing

),

_buildNotesView(context),

const SliverToBoxAdapter(

child: SizedBox(height: 80.0), // bottom spacing make sure the content can scroll above the bottom bar

),

],

),

floatingActionButton: _fab(context),

bottomNavigationBar: _bottomActions(),

floatingActionButtonLocation: FloatingActionButtonLocation.endDocked,

extendBody: true,

),

);

/// A floating appBar like the one of Google Keep

Widget _appBar(BuildContext context) => SliverAppBar(

floating: true,

snap: true,

title: _topActions(context),

automaticallyImplyLeading: false,

centerTitle: true,

titleSpacing: 0,

backgroundColor: Colors.transparent,

elevation: 0,

);

Widget _topActions(BuildContext context) => Container(

width: double.infinity,

padding: const EdgeInsets.symmetric(horizontal: 20),

child: Card(

elevation: 2,

child: Padding(

padding: const EdgeInsets.symmetric(vertical: 5),

child: Row(

children: <Widget>[

const SizedBox(width: 20),

const Icon(Icons.menu),

const Expanded(

child: Text('Search your notes', softWrap: false),

),

InkWell(

child: Icon(_gridView ? Icons.view_list : Icons.view_module),

onTap: () => setState(() {

_gridView = !_gridView; // switch between list and grid style

}),

),

const SizedBox(width: 18),

_buildAvatar(context),

const SizedBox(width: 10),

],

),

),

),

);

Widget _bottomActions() => BottomAppBar(

shape: const CircularNotchedRectangle(),

child: Container(

height: kBottomBarSize,

padding: const EdgeInsets.symmetric(horizontal: 17),

child: Row(

...

),

),

);

Widget _fab(BuildContext context) => FloatingActionButton(

child: const Icon(Icons.add),

onPressed: () {},

);

Widget _buildAvatar(BuildContext context) {

final url = Provider.of<CurrentUser>(context)?.data?.photoUrl;

return CircleAvatar(

backgroundImage: url != null ? NetworkImage(url) : null,

child: url == null ? const Icon(Icons.face) : null,

radius: 17,

);

}

/// A grid/list view to display notes

Widget _buildNotesView(BuildContext context) => Consumer<List<Note>>(

builder: (context, notes, _) {

if (notes?.isNotEmpty != true) {

return _buildBlankView();

}

final widget = _gridView ? NotesGrid.create : NotesList.create;

return widget(notes: notes, onTap: (_) {});

},

);

Widget _buildBlankView() => const SliverFillRemaining(

hasScrollBody: false,

child: Text('Notes you add appear here',

style: TextStyle(

color: Colors.black54,

fontSize: 14,

),

),

);

/// Create the notes query

Stream<List<Note>> _createNoteStream(BuildContext context) {

final uid = Provider.of<CurrentUser>(context)?.data?.uid;

return Firestore.instance.collection('notes-$uid')

.where('state', isEqualTo: 0)

.snapshots()

.handleError((e) => debugPrint('query notes failed: $e'))

.map((snapshot) => Note.fromQuery(snapshot));

}

}


코드는 조금 장황합니다. 여기 에 Google Keep 에서 의 floating AppBar 제공합니다.



NotesGrid 와 NotesList 을 위해서, 그들은 매우 비슷한 것 같습니다. 단지 각각 SliverGrid 와 SliverList 의 wrappr 의 한 종류 입니다.


notes_grid.dart


class NotesGrid extends StatelessWidget {

final List<Note> notes;

final void Function(Note) onTap;

const NotesGrid({Key key, this.notes, this.onTap}) : super(key: key);

/// A static factory method can be used as a function reference

static NotesGrid create({Key key, this.notes, this.onTap}) =>

NotesGrid(key: key, notes: notes, onTap: onTap);

@override

Widget build(BuildContext context) => SliverGrid(

...

);

}


자세한 코드를 모두 여기에 게시하지는 않습니다. 내 https://github.com/xinthink/flutter-keep GitHub 저장소 에서 전체 예제를 찾으십시오 .

모든 것이 잘되면, 이제 직접 만든 Flutter Keep 앱 에서 첫 번째 노트를 볼 수 있습니다!


1*kov0KSVUbhuqVP3pCoebRw.png Flutter Keep screenshot


우리는 지금까지 잘하고 있습니다. provider 패키지를 사용하여 간단한 반응 형 앱을 구축하고 Firebase 툴킷을 사용하는 방법도 배웠습니다.


그러나 응용 프로그램은 메모 편집기가 없으면 유용하지 않습니다. 시리즈의 다음 부분에서 더 많은 기능을 추가 할 것입니다.


To Be Continued ....


https://www.twitter.com/FlutterComm

keyword
매거진의 이전글[번역] 빠르게 코딩을 배우는 팁들