FlutterでbottomNavigationBarを実装する
ネイティブアプリでよく見る、画面下部にあるナビゲーションバーのことを、ボトムナビゲーションバーというそうです!
ナビゲーションバーだとアンドロイドで言うと「◀」「●」「■」が並ぶエリアと呼び方がかぶってしまうので、「ボトムナビゲーションバー」が呼び名なのだとか。
ScaffoldでbottomNavigationBar
を指定します。
class _MyHomePageState extends State<MyHomePage> { int _currentIndex = 0; // currentIndexにデフォルト値を与えないとコンパイルエラーになる @override Widget build(BuildContext context){ return Scaffold( appBar: AppBar( title: Text('Title'), ), body: Center( child: Text('bottomNavigationBar test') ), bottomNavigationBar: BottomNavigationBar( currentIndex: _currentIndex, items: [ new BottomNavigationBarItem( icon: new Icon(Icons.home), title: Text("Home") ), new BottomNavigationBarItem( icon: new Icon(Icons.settings), title: Text("Settings") ), ], onTap: (int index) { print(index); // デバッグ用にprintしてみる }, ), ); } }
こんな感じで、実装はシンプル。
特徴的なのは、onTapがBottonNavigationBarItem(アイコン)側ではなくて、BottomNavigationBar側で準備されていること。
onTapは引数としてintを持っているので、このintを元に画面遷移を実装するようです!
Navigatorクラスを使って実装していくようですが、続きはまた明日やっていきます!
Gitでユーザ名とかパスワードを求められた時
新しいパソコンでGitの設定を間違ったり忘れたりしていて、少し戸惑ったのでメモ。
- git pullでPermission denied
$ git pull git@github.com: Permission denied (publickey). fatal: Could not read from remote repository. Please make sure you have the correct access rights and the repository exists.
SSH keyの登録を忘れていました😇
GitHubでssh接続する手順~公開鍵・秘密鍵の生成から~ - Qiitaを参考に公開鍵・秘密鍵のセットを作って登録します。
- git pushでユーザ名とパスワードを聞かれる
remote urlでhttpsを使用していました😇
sshを使って接続します。
git remote -v origin https://github.com/makicamel/test.git (fetch) origin https://github.com/makicamel/test.git (push)
GitHubのリポジトリの「Clone or download」ボタンを押した時に「Clone with SSH」と表示されていたら「Use SSH」をクリック、表示されたgit:github.com
から始まるアドレスをコピー。
git remote set-url origin git@github.com:makicamel/test.git git remote -v origin git@github.com:makicamel/test.git (fetch) origin git@github.com:makicamel/test.git (push)
- git pullでパスフレーズを聞かれる
秘密鍵にパスフレーズを設定していました😇
ssh-agentに秘密鍵を登録します。
ssh-add ~/.ssh/id_rsa
- 参考リンク
ssh-agentを利用して、安全にSSH認証を行う - Qiita
それでは快適なGitライフを!
Androidの仮装端末からインターネットに接続できない時の対処法
今日は(も)小ネタ。
Androidアプリの開発中、たまによくある(らしい)Wifiがつながらない事案。
そんな時はapp/src/main/AndroidManifest.xml
に以下を記述します。
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.hoge_app"> <uses-permission android:name="android.permission.INTERNET" /> <!-- Added -->
アプリのインターネットへの接続を行うことができるようになる設定と教えていただきました!
ただ今回の原因はアプリではなく仮装端末自体がWifiにつながっておらず、
理由は不明で仮装端末の再起動で直りました😅
Androidの実機の際はWifiは端末(実機)依存なのだそうですが、
仮装端末だと起こりがちな事象だというお話を伺いました!
flutter clean
& エミュレータの再起動を習慣にしようと改めて思った事案でした!
間違ってremoteにpushしたファイルを削除したい時
誤ってパスワードなどの秘密情報が含まれたファイルをremoteにpushしてしまった時。
普通にgit rm hoge
しただけだと当然ながら履歴が残ってしまうので、履歴ごと改変したい時のやり方です。
リモートの履歴を改変することになるので、チーム開発の際は混乱が起きない様ご注意ください。
前提条件
こんな履歴があったとします。
$ git log --oneline fae133a (HEAD -> master, origin/master) 3rd commit. 3ee314f 2nd commit. a3af1e4 Initial commit.
よく見ると、パスワードが書かれたsecret.file
をcommit(&push)しています!
$ git log -p -2 commit fae133a633e21e7feb94c3994c276ef86d41210c (HEAD -> master, origin/master) Author: makicamel Date: Tue Mar 19 19:00:30 2019 +0900 3rd commit. # (略) commit 3ee314fc2f06bb7118229dc25ece3b54af889fb4 Author: makicamel Date: Tue Mar 19 19:00:00 2019 +0900 2nd commit. diff --git a/secret.file b/secret.file index e69de29..9c3f6c8 100644 --- a/secret.file +++ b/secret.file @@ -0,0 +1 @@ +PASSWORD # <= これを消したい!
git rm secret.file
をするとファイルは消せますが、履歴は消せません。
履歴自体を消したいので、commitからやり直します。
commitを取り消し
まずはローカルで変更を修正。
git log
で戻りたい履歴のコミットのハッシュ値を確認、そこまで戻ります。
git reset
のオプションはこちらに詳しいですが--hard
、--mixed
、--soft
とあります。
$ git log --oneline fae133a (HEAD -> master, origin/master) 3rd commit. 3ee314f 2nd commit. # <= ここでsecret.fileを追加した a3af1e4 Initial commit. # <= ここに戻れればOK
今回はファイルの変更はそのまま、コミットだけ取り消したいので--soft
します。
$ git reset a3af --soft $ git log --oneline a3af1e4 (HEAD -> master) Initial commit.
ファイルをgit管理対象から外す
.gitignore
に外したいファイルを追記。
$ echo "secret.file" > .gitignore
ただしこのままpushしても、gitが管理してくれているままになるので、git rm
します。
git rm
だとファイルごと削除されるので、ファイルを残すよう--cached
オプションをつけて実行。
$ git rm --cached secret.file
remoteにpush
あとはremoteにpushするだけ!
リモートとローカルで不整合が起きるので、-f
オプションでpushします。
$ git push -f
無事履歴からファイルを削除することができました!
入門!アトミックデザインに行ってきました
今日は@nrsさんのAtomicDesign入門に行ってきました!
振り返りと理解の確認のためにまとめを書かせて頂きます。
登壇資料はこちら。
AtomicDesignとは
モジュール分割を基本としたデザインシステム(デザインの指針・デザインの共通言語)のこと。
モジュールは粒度によって下記の5つのステージに分けられる。
- Atoms 原子: 最小のコンポーネント。ex. Logo, TextBox
- Molecules 分子: ふたつ以上のAtomsを組み合わせたコンポーネント。
ex. HeaderLink(Logo + HeaderTitle), SearchBox(TextBox + PrimaryButton) - Organisms 生物: AtomsとMoleculesを組み合わせたコンポーネント。
ex. Header(HeaderLink + SearchBox) - Templates ページの基礎となるコンテンツ構造、ワイヤーフレームのこと。
ex.Header + BreadCrampList + Contents - Pages テンプレートにデータを流し込んだもの
※ Templatesから「原子どこ行った??」状態なのは、Atom〜Organismsまではエンジニア/デザイナー向け、Templates以上は上流のお歴々と話をする為の道具となるからじゃないでしょうか、とのこと。
※ Atomとは、「UIとしての機能性を破壊しない最小要素」。
もしButtonは矩形とラベルにわけると、ラベルは押せないのでButtonとしての機能を失う。
だから、Buttonが最小要素(Atom)。
なるほどわかりやすい!
けどやっぱり、そうシンプルな話でもない。
AtomicDesignは5階層に分けることでモジュールの乱立を防ぐことができることが重要なのだけど、いつでもAtom -> Molecules -> Organismsと分けられるわけではない。
たとえば、こんなページがあったとすると
Atoms: TableCell, PagingButton Molecules: TableRow(TableCellの集まり), PagingMenu(PagingButtonの集まり) Atoms: TabButton, TabContent Molecules: TabMenu(TabButtonの集まり)
Organismはこうなる
Organisms: Table(TableRow + PagingMenu), Tab(TabMenu + TabContent)
でも、Organism同士であるTableとTabを足しても、これはTemplateとは言えない。
(HeaderとかFooterとかSideMenuとか足りない)
…おや?🤔
AtomicDesignは使えないのか?
わけではない。
AtomicDesignはデザインシステムである。
AtomicDesignの目的
- ❌AtomicDesignに沿って作ること
- ⭕デザイナーとエンジニアの協業のための共通基盤づくり
-> 共通認識のもとで会話ができるようになること!
コンポーネント指向
すべてはコンポーネントであるという考え方
Webだと、image
, textbox
, button
etc, ぜんぶ組み合わせで作れてしまう。
ゲームやネイティブアプリだと、全てコンポーネントを作る(必要がある)。
AtomicDesignとは粒度にルールを加えたコンポーネント指向のこと。
たとえばよくあるこんなケース
デザイナー💁「モーダルに『閉じる』ボタンをつけたい!」
コンポーネント化していないと
エンジニア「いくつあるのかわからないモーダル、全部修正してテストするのか😇」
コンポーネント化していると
エンジニア「Modalコンポーネントにつければいけるぜ😋」
AtomicDesignのメリット: 変更に強くなる
AtomicDesign実践入門
どこからつくる?🤔
- サンプルページを作る
デザイナー💁「こんな感じにしよっか」 - Atomの切り出しの検討
- 組み合わせてMoleculesを作る
- 組みあわせてOrganismsを作る
2〜4の工程をデザイナーとエンジニアを一緒にやれたらベスト!!
(おすすめはホワイトボードだけど、Web上でやったりとかでもOK) - 4まで共通認識が出揃ったら各デザイナー、エンジニアが実装
※ プロパティやイベントを持った最小要素(Atoms)を仮実装->Molecules->Organismsと進める
(下位要素があれば上位要素もそれで作れるから)
※ 同じレイヤーの作業は別の人と重複することがあるので、作業分担は最初に要素を洗い出すことが大事 - Template
Templateのコード化は必須ではないが、もしするなら全体のTemplateとコンテンツのTemplateで分けるのがおすすめ - Organismsの粒度
さっきの例のように、Atom -> Molecules -> Organismの三段階では足りなくなるパターンがある。
→ OrganismはOrganismをコンポジションする
つまり、Organism + Organism = Organismもアリ
AtomicDesignは5階層であることに意味があるけれど、忠実にすることが難しさにつながるならその本質を見極めて取捨選択するべき。
AtomicDesignは守ることを目的とした不変の法律ではなく、開発に関わる全ての人達の指標。
目的はデザイナーとエンジニアの協業のための共通基盤づくり。
指標があるとお互いがお互いをリスペクトして仕事しやすくなる。
まとめ
- AtomicDesignとは
粒度にルールを加えたコンポーネント指向のこと - AtomicDesignのメリット
変更に強くなる、保守性があがること - AtomicDesignの目的
デザイナーとエンジニアが共通認識のもとで会話・協業ができるようになること
デザイナー💁「これに従ってデザインするね」
エンジニア😍「やりやすい!」
こんな会話が生まれるためのデザインシステム、それがAtomicDesign。
AtomicDesignとは、を学ばせて頂くと同時に、終始「よりよくするには?」を根底に感じる1時間でした。
ありがとうございました!!
参考: 原典AtomicDesign
Flutterでgoogleにログインする
毎日Flutter。
今日はFirebaseで認証をします。
Pubはこれ(firebase_auth)ですね。
google_sign_inを追加する
まずはgoogle_sign_inを追加します。
pubspec.yaml
に下記追記して、flutter packages get
。
dependencies: flutter: sdk: flutter image_picker: google_sign_in: ^4.0.1+1 # new! firebase_storage:
main.dart
にimport
を追記します。
import 'package:google_sign_in/google_sign_in.dart';
firebase_authを追加する
build.gradle
にclasspathを追記するのは前回やったので省略。
pubspec.yaml
とmain.dart
にそれぞれ下記追記します。pubspec
変更後はflutter packages get
。
dependencies: flutter: sdk: flutter image_picker: google_sign_in: ^4.0.1+1 firebase_auth: # new! firebase_storage:
import 'package:firebase_auth/firebase_auth.dart';
ログイン画面の追加
main.dart
に下記を追記し、MyApp
クラスからMyGoogleLoginPage()
を呼び出すように変更します。
class MyGoogleLoginPage extends StatefulWidget { @override _MyGoogleLoginPageState createState() => _MyGoogleLoginPageState(); } class _MyGoogleLoginPageState extends State<MyGoogleLoginPage>{ final GoogleSignIn _googleSignIn = GoogleSignIn(); final FirebaseAuth _auth = FirebaseAuth.instance; Future<FirebaseUser> _handleSignIn() async { final GoogleSignInAccount 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 _auth.signInWithCredential(credential); print("signed in " + user.displayName); return user; } @override Widget build(BuildContext context){ return Scaffold( appBar: AppBar( title: Text('none'), ), body: Center( child: Column( children: <Widget>[ SizedBox(height: 24.0), FlatButton( child: Text('SignIn'), onPressed: () => _handleSignIn() .then((FirebaseUser user) => print(user)) .catchError((e) => print(e)) ), ], ), ), ); } }
googleにログインができるようになりました!
FlutterでFirebaseのStorageを使う〜後編〜
昨日(FlutterでFirebaseのStorageを使う〜前編〜)の続きです。
AndroidXに対応する
さて、昨日、Firebaseを使うようbuild.gradle
に設定を加えたところ、大量のエラーが出てビルドができなくなりました😨
エラーメッセージ中に「ここ見てね!」とリンクが記述されているので、素直に見に行きます。
ざっくりいうと、android.supportというライブラリが非推奨になって、AndroidXってライブラリに変更されたよ!とのこと。
migrateの方法なども記載されているのですが、自動でAndroidX化してくれる方法を載せてくれている人がいたのでその方法で対応します。
# android/gradle.propertiesに下記を追加 android.useAndroidX = true android.enableJetifier = true
無事ビルドが通るようになりました!
firebase_storageをインポート
Pubでそれっぽいものを探すと、firebase_storageが出てきます。これだ!
image_pickerと違ってExampleは書かれていないので、手探りでやります💪
まずはpubspec.yaml
にfirebase_storage
を追加。
dependencies: flutter: sdk: flutter image_picker: firebase_storage: # new!
main.dart
でインポートします。
import 'package:firebase_storage/firebase_storage.dart';
Flutter製チャットアプリを支える技術を参考にuploadImage
メソッドを書きます。
(格好悪いけど、直接getImageから呼び出す)
class _MyHomePageState extends State<MyHomePage> { Future getImage() async { // (略) uploadImage(image); } Future<String> uploadImage(File file) async { int timestamp = DateTime.now().millisecondsSinceEpoch; String subDirectoryName = 'images'; final StorageReference ref = FirebaseStorage() .ref() .child(subDirectoryName) .child('${timestamp}'); final StorageUploadTask uploadTask = ref.putFile( file, StorageMetadata( contentType: "image/jpeg", )); StorageTaskSnapshot snapshot = await uploadTask.onComplete; if (snapshot.error == null) { return await snapshot.ref.getDownloadURL(); } else { return 'Something goes wrong'; } } // (略) }
※ 実行の際にMissingPluginException(No implementation found for ...)
というエラーがでる場合は、flutter clean & flutter packages get & flutter run
でうまくいく場合があります。
さて、実行すると、firebase-auth is not linked
、no auth token for request
といったメッセージが表示されますが、画像のアップロードに関してのエラーはないみたい!
わくわくしながらFirebaseのStorageを見てみると、タイムスタンプの名前のついた画像がアップロードされました!🎉