11
9

More than 3 years have passed since last update.

【Flutter】Flutter × Firebase Firestore で単語帳アプリを作成

Posted at

完成イメージ

githubはこちら

完成イメージ

作業手順

  1. firebaseをアプリに追加
  2. firestoreを設定
  3. ソースコード書き書き(Firestoreデータの読み取り)

firebaseをflutterアプリに設定

こちらに関しては公式のほうが丁寧に説明されておりますので以下のGoogle公式サイトを参照してください。
Flutter アプリに Firebase を追加する

firestoreを設定

Firestore プラグインの追加

pubspec.yaml
dependencies:
  flutter:
    sdk: flutter
  firebase_core: ^0.3.0
  cloud_firestore: ^0.9.5

パッケージのimport

main.dart
import 'package:cloud_firestore/cloud_firestore.dart';

トップ画面の読み取り

要点はStreamBuilderを使用して、'category'という名前のテーブルを取得するところです。

Firestore.instance.collection('category').snapshots()

また、アイテムをタップ時にタップされたアイテムの情報を画面遷移の際に以下のように渡しています。

                      onTap: () {
                        Navigator.push(
                          context,
                          MaterialPageRoute(builder: (context) => TangoListDetailScreen(categoryName: categolies[index]["name"], categoryID: categolies[index].documentID,)),
                        );
                      }

Firestoreのデータはこのような感じです。
image.png

main.dart
@override
  Widget build(BuildContext context) {
    return new Container(
        decoration: new BoxDecoration(
            color: Colors.white
        ),
        child: StreamBuilder<QuerySnapshot>(
          stream: Firestore.instance.collection('category').snapshots(),
          builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
            if (snapshot.hasError)
              return new Text('Error: ${snapshot.error}');
            switch (snapshot.connectionState) {
              case ConnectionState.waiting: return new Text('Loading...');
              default:
                List<DocumentSnapshot> categolies = snapshot.data.documents;
                return ListView.builder(
                  padding: EdgeInsets.fromLTRB(30, 10, 30, 10),
                  itemCount: categolies.length,
                  itemBuilder: (BuildContext context, int index) {

                    return GestureDetector(
                      child: Container(
                        margin: EdgeInsets.only(bottom: 20.0),
                        height: 97,
                        decoration: new BoxDecoration(
                          boxShadow: [BoxShadow(
                              color: Color(0x29000000),
                              offset: Offset(0,3),
                              blurRadius: 6,
                              spreadRadius: 0
                          ) ],
                        ),
                        child: Card(
                          shape: RoundedRectangleBorder(
                            borderRadius: BorderRadius.all(Radius.circular(10)),
                          ),
                          child: Container(
                              decoration: new BoxDecoration(
                                color: Color(0xffffffff),
                                border: Border.all(
                                    color: Color(0xff007aff),
                                    width: 0.5
                                ),
                              ),
                              child: Center(
                                child: Text(
                                    categolies[index]["name"],
                                    style: TextStyle(
                                      fontSize: 25,
                                    )
                                ),
                              )
                          ),
                        ),
                      ),
                      onTap: () {
                        Navigator.push(
                          context,
                          MaterialPageRoute(builder: (context) => TangoListDetailScreen(categoryName: categolies[index]["name"], categoryID: categolies[index].documentID,)),
                        );
                      }
                    );
                  },
                );
            }
          },
        ),
    );
  }

単語一覧画面の設定

単語のDBはこのような構成
image.png

import 'package:flutter/material.dart';
import 'TangoScreen.dart';
import 'package:cloud_firestore/cloud_firestore.dart';

class TangoListDetailScreen extends StatelessWidget {
  TangoListDetailScreen({this.categoryName, this.categoryID});
  final String categoryName;
  final String categoryID;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: TangoListDetailWidget(categoryName: categoryName, categoryID: categoryID,),
    );
  }
}

class Header extends StatelessWidget with PreferredSizeWidget{
  Header({this.title});
  final String title;

  @override
  Size get preferredSize => Size.fromHeight(kToolbarHeight);

  @override
  Widget build(BuildContext context) {
    return AppBar(
      iconTheme: IconThemeData(
        color: Colors.black,
      ),
      title: Text(
        title ?? '',
        style: TextStyle(
          color: Colors.black,
          fontSize: 20,
        ),
      ),
      backgroundColor: Colors.grey[200],
      centerTitle: true,
      elevation: 0.0,
    );
  }
}

class TangoListDetailWidget extends StatefulWidget {
  TangoListDetailWidget({this.categoryName, this.categoryID});
  final String categoryName;
  final String categoryID;

  @override
  State<StatefulWidget> createState() {
    return ListState(categoryName: categoryName, categoryID: categoryID);
  }
}

class ListState extends State<TangoListDetailWidget> {
  ListState({this.categoryName, this.categoryID});
  final String categoryName;
  final String categoryID;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: Header(title: categoryName),
      body: StreamBuilder<QuerySnapshot>(
        stream: Firestore.instance.collection('words').where('categories', arrayContains: categoryID).snapshots(),
        builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
          if (snapshot.hasError)
            return new Text('Error: ${snapshot.error}');
          switch (snapshot.connectionState) {
            case ConnectionState.waiting: return new Text('Loading...');
            default:
              List<DocumentSnapshot> words = snapshot.data.documents;
              return ListView.builder(
                itemBuilder: (BuildContext context, int index) {

                  if (index == 0) {
                    return Container(
                      width: 375,
                      height: 110,
                      decoration: new BoxDecoration(
                          color: Color(0xff098a8c),
                          border: Border.all(
                              color: Color(0xff098a8c),
                              width: 1
                          )
                      ),
                      child: Container(
                        margin: EdgeInsets.only(right: 10, bottom: 10),
                        child: Row(
                            crossAxisAlignment: CrossAxisAlignment.start,
                            verticalDirection: VerticalDirection.up,
                            textDirection: TextDirection.rtl,
                            children: [
                              Expanded(
                                child: Text(categoryName,
                                  style: TextStyle(
                                    fontFamily: 'SFProDisplay',
                                    color: Color(0xffffffff),
                                    fontSize: 30,
                                    fontWeight: FontWeight.w400,
                                    fontStyle: FontStyle.normal,
                                    letterSpacing: 0.0075,
                                  ),
                                  textAlign: TextAlign.right,
                                  overflow: TextOverflow.ellipsis,
                                ),
                              ),
                            ]
                        ),
                      ),
                    );
                  }

                  return Container(
                      decoration: BoxDecoration(
                        border: Border(
                          bottom: BorderSide(color: Colors.black38),
                        ),
                      ),
                      child: ListTile(
                        trailing: Image.asset('assets/right_detail_grey.png'),
                        title: Text(
                          words[index - 1]["indonesia"],
                          overflow: TextOverflow.ellipsis,
                        ),
                        onTap: () {
                          Navigator.push(
                            context,
                            MaterialPageRoute(builder: (context) => TangoScreen(categoryName: categoryName, categoryID: categoryID, currentIndex: index - 1,)),
                          );
                        },
                      ));},
                itemCount: words.length + 1,
              );
          }
        },
      ),
    );
  }
}

単語画面の設定

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

class TangoScreen extends StatelessWidget {
  TangoScreen({this.categoryName, this.categoryID, this.currentIndex});
  final String categoryName;
  final String categoryID;
  final int currentIndex;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: TangoWidget(categoryName: categoryName, categoryID: categoryID, currentIndex: currentIndex),
    );
  }
}

class Header extends StatelessWidget with PreferredSizeWidget{
  @override
  Size get preferredSize => Size.fromHeight(kToolbarHeight);

  @override
  Widget build(BuildContext context) {
    return AppBar(
      iconTheme: IconThemeData(
        color: Colors.black,
      ),
      actions: [
        Padding(
          padding: const EdgeInsets.only(right: 16.0),
          child: Row(
            children: [
              GestureDetector(
                child: Container(
                  margin: EdgeInsets.only(left: 5.0, right: 5.0),
                  child: Image.asset('assets/checkmark_lightgrey.png'),
                ),
                onTap: () {

                },
              ),
            ],
          )
        ),
      ],
      backgroundColor: Colors.white,
      centerTitle: true,
      elevation: 0.0,
    );
  }
}

class TangoWidget extends StatefulWidget {
  TangoWidget({this.categoryName, this.categoryID, this.currentIndex});
  final String categoryName;
  final String categoryID;
  final int currentIndex;

  @override
  State<StatefulWidget> createState() {
    return TangoState(categoryName: categoryName, categoryID: categoryID, currentIndex: currentIndex);
  }
}

class TangoState extends State<TangoWidget> {
  TangoState({this.categoryName, this.categoryID, this.currentIndex});
  final String categoryName;
  final String categoryID;
  int currentIndex = 0;
  bool isFront = true;

  @override
  Widget build(BuildContext context) {
    return StreamBuilder<QuerySnapshot>(
      stream: Firestore.instance.collection('words').where('categories', arrayContains: categoryID).snapshots(),
      builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
        if (snapshot.hasError)
          return new Text('Error: ${snapshot.error}');
        switch (snapshot.connectionState) {
          case ConnectionState.waiting: return new Text('Loading...');
          default:
            List<DocumentSnapshot> words = snapshot.data.documents;
            return Scaffold(
              appBar: Header(),
              body: Container(
                alignment: Alignment.center,
                child: Text(
                  isFront ? words[currentIndex]["indonesia"] : words[currentIndex]["japanese"],
                  style: TextStyle(
                    fontFamily: 'SFProDisplay',
                    color: Colors.black,
                    fontSize: 40,
                    fontWeight: FontWeight.w400,
                    fontStyle: FontStyle.normal,
                    letterSpacing: 0.0075,
                  ),
                  overflow: TextOverflow.visible,
                ),
              ),
              bottomNavigationBar: BottomAppBar(
                child: Row(
                  mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                  children: [
                    GestureDetector(
                      child: Container(
                        margin: EdgeInsets.all(15),
                        child: Image.asset('assets/left_arrow_big.png'),
                      ),
                      onTap: currentIndex == 0 ? null : () {
                        setState(() {
                          isFront = true;
                          currentIndex--;
                        });
                      },
                    ),
                    GestureDetector(
                      child: Container(
                        margin: EdgeInsets.all(15),
                        child: Image.asset('assets/return.png'),
                      ),
                      onTap: () {
                        setState(() {
                          isFront = !isFront;
                        });
                      },
                    ),
                    GestureDetector(
                      child: Container(
                        margin: EdgeInsets.all(15),
                        child: Image.asset('assets/right_arrow_big.png'),
                      ),
                      onTap: currentIndex + 1 >= words.length ? null : () {
                        setState(() {
                          isFront = true;
                          currentIndex++;
                        });
                      },
                    ),
                  ],
                ),
              ),
            );
        }
      },
    );
  }
}

参考記事

11
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
9