Skip to content

Instantly share code, notes, and snippets.

@mtskf
Last active January 24, 2023 02:09
Show Gist options
  • Save mtskf/7053058999b39a9c0c19f439fbbe1124 to your computer and use it in GitHub Desktop.
Save mtskf/7053058999b39a9c0c19f439fbbe1124 to your computer and use it in GitHub Desktop.
[Dart & Flutter] #Flutter #Dard

Types

String? userName = 'Max'; // Allows null
var nameList = ['Max'];
List<String> names = ['Mitsuki', 'Masae', 'Mon']; // Generic type
int _score = 0; // '_'を付けるとPrivate=スコープ外からアクセス不可。
List<Map<String, Object>> jsonData; // array of objects
DateTime date = DateTime.now(); // get date
String dateStr = DateTime.now().toIso8601String();

// 型変換
int.parse('122');
double.parse('0.8');
DateTime.parse('2020-09-30 12:30:50');

123.456.toInt(); // 👉 123
123.toDouble();  // 👉 123
123.999.toInt(); // 👉 123

123.456.toString();         // 👉 '123.456'
123.456.toStringAsFixed(2); // 👉 '123.45'

static, final, const

  • static: インスタンスごとに保持されるのではなく、クラスで共有されるmutable変数。
  • final: 初期化後はimmutable。コンストラクタで初期値を設定できる。リストの中身は変更可。
  • const: リストやコレクションで使う。中身も全てコンパイル時からimmutable。

Variables

  • val.isEmpty()
  • str.startsWith('...')
  • str.endsWith('...')
  • double.tryParse(val)

#### String https://zenn.dev/tris/articles/bf623e5e65fac3

List

Getter

  • .reversed
  • .isEmpty
  • .isNotEmpty
  • .first, .last

Methods

  • .contains(...) // some
  • .firstWhere(o => ...) // find
  • .lastWhere(o => ...) // find last
  • .where(o => ...) // filter
  • .removeWhere(o => ...)
  • .add() // push
  • .addAll([...]) // concat
  • .insert(0, ...) // unshift
  • .sublist(startIndex, endIndex) // slice
  • .clear() // make it empty
  • .reduce((a, b) => a + b); // 合計
  • .any(). // some
  • .every() // every

Map

  • .containsKey(key)
  • .putIfAbsenst(key, (item) => ...)
  • .update(key, (item) => ...)
  • .forEach((key, item) => ...)

Conditionals

For-in

for (var tx in recentTransactions) {
  print(tx);
}

Functions

...

Class & Mixin

  • Mixin: used to inject extra utility properties & methods
  • Mixinは , で区切って複数追加できるよ
mixin Agility {
  var speed = 10;
  void sitDown() {
    print('Sitting down...');
  }
}

class Mammal {
  void breathe() {
    print('Breathe in... breathe out...');
  }
}

class Person extends Mammal {
  final String name; // 'final' suggests immutable after instantiation
  int age;
  int _score; // '_'を付けるとPrivateになる=外部からアクセス不可。

  // Constructor
  Person({
    @require this.name,
    this.age = 30,
  });

  // Special Constructor
  Person.veryOld(this.name) {
    age = 60;
  }

  // Overwriting methods...
  // @overwrite
  // void breathe() { ... }

  void addScore(score) {
    _score += score
  }

  void greet() {
     print('Hi, I am ' + name + ' and I am ' + age.toString() + ' years old!');
  }

  // getter
  String get score => _score;

  // setter -> eg) p1.score = 12;
  set score(int score) {
    _score = score;
  }
}

void main() {
  var p1 = Person(age: 40, name: 'Manu');
  var p2 = Person.veryOld('John');
  p1.breathe();
  p1.greet();
  p2.greet();
}

Regex

var urlPattern = r"(https?|ftp)://([-A-Z0-9.]+)(/[-A-Z0-9+&@#/%=~_|!:,.;]*)?(\?[A-Z0-9+&@#/%=~_|!:‌​,.;]*)?";
var result = new RegExp(urlPattern, caseSensitive: false).firstMatch('https://www.google.com');

HTTP Request

  • use http package
final url = Uri.parse('https://flutter-update.firebaseio.com/products.json');
http.post(url, body: json.encode({
  'name': ...,
  'age': ...,
  ...
})).then((res) {
  final data = joson.decode(res.body);
  // do something...
});

or

final url = Uri.https('flutter-update.firebaseio.com', '/products.json');
http.post(url, ...);

async & await を使うと…

Future<void> fetchProducts() async {
  const url = '....';
  try {
    final res = await http.get(url);
    print(res.body);
    // do something...
  } catch (err) {
    throw(err);
  }
}

長い res をprintしたい時は、、、

void printWrapped(String text) {
  final pattern = RegExp('.{1,800}'); // 800 is the size of each chunk
  pattern.allMatches(text).forEach((match) => print(match.group(0)));
}

printWrapped(res);

Setup & Dev

Get started

❯ flutter create project_name
❯ flutter run
  • VSCode > Run > Start Debugging
  • Config -> pubspec.yaml
  • Package追加 -> ❯ dart pub add ...

Upgrade Flutter

❯ flutter upgrade
❯ flutter clean
❯ flutter pub upgrade

Fix iOS build errors

❯ cd ios
❯ rm Podfile
❯ rm Podfile.lock
❯ pod init
❯ pod install

それでダメなら。。。

❯ cd ios
❯ rm Podfile
❯ rm Podfile.lock
❯ rm -rf ~/Library/Developer/Xcode/DerivedData/*
❯ flutter clean
❯ pod init
❯ pod install
❯ flutter build ios

https://stackoverflow.com/questions/68434062/flutter-ios-module-cloud-firestore-not-found-in-generatedpluginregistrant

まとめて:

❯ rm Podfile.lock && pod update && rm -Rf ~/Library/Developer/Xcode/DerivedData/* && flutter clean && flutter pub get && flutter build ios && pod install && flutter build ipa

それでも上手くいかず、これやったら治った。なんかよくわからんけど。。。 firebase/flutterfire#7987

rm -Rf ios/Pods
rm -Rf ios/.symlinks
rm -Rf ios/Flutter/Flutter.framework
rm -Rf ios/Flutter/Flutter.podspec
rm ios/Podfile
flutter run

rm -Rf ios/Pods && rm -Rf ios/.symlinks && rm -Rf ios/Flutter/Flutter.framework && rm -Rf ios/Flutter/Flutter.podspec && rm ios/Podfile && flutter run

deploy

flutter build ipa

/build/ios/archive/Runner.xcarchive を開いて "Distribute App"

VSCode shortcuts

  • ⌘ + I — Trigger suggest
  • ⌘ + R — Refactor

Debugging

  • use Dart Dev tools

Install on iOS devices

❯ flutter clean
❯ flutter build ios
❯ flutter devices
❯ flutter install -d {デバイスIDの頭文字}

Apple Certificates

Widgets

Init app (top level widget)

// show "MyApp" widget
void main() => runApp(MyApp());

StatelessWidget

class MyWidget extends StatelessWidget {
  final int score; // statelessではfinalを使おう。

  // Constructor
  MyWidget(this.score); // 親ウィジェットから変数を受け取る

  @override
  Widget build(BuildContext context) {
    return ....
  }
}

StatefulWidget

class MyWidget extends StatefulWidget {
  // this widget will be re-created when input data changes...
  @override
  State<MyWidget> createState() => _MyWidgetState();
}

class _MyWidgetState extends State<MyWidget> {
  // this widget won't be re-created,
  // thus the state variabes here win't be reset.
  int _totalScore = 0;

  void _addTotalScore (int score) {
    // force re-render the widget when state is updated
    setState(() => _totalScore += score);
  }

  Widget build(BuildContext context) {
    return ....
  }
}

User input

  • Controller で入力値をReactiveに設定 -> .text で入力値を取得
  • もしくは Form widget を使おう。
  • Controller は使い終わったら必ず dispose() すること。
class MyWidget extends StatelessWidget {
  final hogeInputController = TextEditingController();

  @override
  void dispose() {
    // disposeするの忘れずに! 忘れるとメモリリークおこるよ。
    hogeInputController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: <Widget>[
        TextField(
          decoration: InputDecoration(labelText: 'Enter text'),
          controller: hogeInputController,
        ),
        TextButton(
          onPressed: () => print(hogeInputController.text),
          child: const Text('Submit'),
        )
      ],
    );
  }
}

Custom images

  • need to add the asset path in pubspec.yaml
  • need to wrapped by other weidget with size specified
Image.asset(
  'assets/images/hoge.png',
  height: 250,
  width: double.infinity,
  fit: BoxFit.cover,
);

// urlで指定する場合は.networkを使う
Image.network(
  imageUrl,
  ...,
);

// backgroundImage の場合は Image provider を使う
backgroundImage: NetworkImage('...)
backgroundImage: AssetImage('...')

Flex Widget

Flexible(
  fit: FlexFit.loose, // flex-grow: 0;
  // fit: FlexFit.tight, // flex-grow: 1;
  flex: 2, // takes up 2 among available space
  child: ...
),
Expanded( // fit:FlexFit.tight と同じ
  flex: 3,
  child: ...
)

List Widgets

// ListView
ListView.builder(
  itemCount: meals.length,
  itemBuilder: (ctx, index) {
    return Text(meals[index].title);
  }
),

// GridView
GridView.builder(
  padding: const EdgeInsets.all(25),
  itemCount: categories.length,
  gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
    maxCrossAxisExtent: 200,
    childAspectRatio: 3 / 2,
    crossAxisSpacing: 20,
    mainAxisSpacing: 20,
  ),
  itemBuilder: (ctx, index) {
    return CategoryItem(
      categories[index].id,
      categories[index].title
    );
  },
),

Styling

Portraitモードのみにする

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

void main() {
  SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
  runApp(MyApp());
}

MediaQuery

final mediaQuery = MediaQuery.of(context)
mediaQuery.size.width > 360 ? ...

OS別のスタイル

import 'dart:io';

Platform.isIOS ? ...

Widget Lifecycle

  • Widget Constructor
  • createState()
  • State Constructor
  • InitState()
  • build()
  • didUpdateWidget()
  • dispose()

App Lifecycle

  • WidgetsBindingObserver Mixin を使う
class MyHomePage extends StatefulWidget {
  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

// WidgetsBindingObserver Mix-in を追加
class _MyHomePageState extends State<MyHomePage> with WidgetsBindingObserver {

  // Observer を登録
  @override
  void initState() {
    super.initState();  // super を先に書くこと!
    WidgetsBinding.instance!.addObserver(this);
  }

  // Observer を削除
  @override
  void dispose() {
    WidgetsBinding.instance!.removeObserver(this);
    super.dispose();  // super を後に書くこと!
  }

  // Lifecycleを取得・操作
  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    print(state);
  }


  @override
  Widget build(BuildContext context) {
  }
}

AppLifecycleState

  • paused -> background になった時
  • inactive -> background
  • resumed -> back visible
  • detached -> ...
  • suspending() -> about to removed from memory eg バッテリー切れとか

initState で async/await

@override
void initState() {
  super.initState();
  Future.delayed(Duration.zero, () async {
    await ...();
  });
}

Routing

Constructorでプッシュ

Navigator.of(context).push(
  MaterialPageRoute(builder: (_) => HogeWidget()),
);

Route Name でプッシュ

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      ....
      initialRoute: '/',  // デフォルトRoute
      routes: {
        // routes を追加
        '/': (ctx) => CategoriesScreen(),  // home
        '/category-meals': (ctx) => CategoryMealsScreen(),
      },
      onGenerateRoute: (settings) {  // フォールバック
        print(settings.arguments);
        print(settings.name);
        return MaterialPageRoute(builder: (ctx) => const CategoriesScreen());
      },
      onUnknownRoute: (settings) {  // さらにフォールバック
        return MaterialPageRoute(builder: (ctx) => const CategoriesScreen());
      },
    );
  }
}
Navigator.of(context).pushNamed('/category-meals', arguments: {
  'id': id,
  'title': title,
});

さらにRoute name を変数にする

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      ....
      initialRoute: '/',  // デフォルトRoute
      routes: {
        // routes を追加
        '/': (ctx) => CategoriesScreen(),  // home
        CategoryMealsScreen.routeName: (ctx) => CategoryMealsScreen(),
      },
    );
  }
}
Navigator.of(context).pushNamed(CategoryMealsScreen.routeName, arguments: {
  'id': id,
  'title': title,
});
class CategoryMealsScreen extends StatelessWidget {
  static const routeName = '/category-meals';  // routeNameを指定

  @override
  Widget build(BuildContext context) {
    return ....;
  }
}
  • .pop(), .canPop() で前のページに戻る
  • .pushReplacement(), .pushReplacementNames() を使うと前のページを消してくれる。Or stack内にページオブジェクトが無限に増え続けるよ。
  • .then(() => ...) は、popで戻ってきた時に実行されるよ。引数を受け取れる。 

State Management

  • Install "provider" package
$ flutter pub add provider
  • Define ChangeNotifier
import 'package:flutter/widgets.dart';
import '../models/product.dart';
import '../dummy_products.dart';

class Products with ChangeNotifier {
  final List<Product> _items = dummy_products;

  List<Product> get items {
    return [..._items];
  }

  Product findById(String id) {
    return _items.firstWhere((prod) => prod.id == id);
  }

  void addProduct(item) {
    _items.add(item);
    notifyListeners();
  }
}
  • Create ChangeNotifierProvider in a widget
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import './screens/products_overview_screen.dart';
import './screens/product_detail_screen.dart';

// import provieder definition
import '../providers/products.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(                 // set notifier
      create: (_) => Products(),   // create provider
      child: MaterialApp(
        title: 'MyShop',
        theme: ThemeData(primarySwatch: Colors.blueGrey),
        initialRoute: ProductOverviewScreen.routeName,
        routes: {
          ProductOverviewScreen.routeName: (ctx) => ProductOverviewScreen(),
          ProductDetailScreen.routeName: (ctx) => ProductDetailScreen(),
        },
      ),
    );
  }
}
  • Use products data in a widget
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../models/product.dart';
import '../widgets/product_item.dart';

// import provieder definition
import '../providers/products.dart';

class ProductGrid extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final productsData = Provider.of<Products>(context);
    final products = productsData.items;
    return GridView.builder(
      itemBuilder: (ctx, i) => ProductItem(
        products[i].id,
        products[i].title,
        products[i].imageUrl,
      ),
      ...,
    );
  }
}

注)create()とvalueコンストラクタの使い分け方

// create method
ChangeNotifierProvider(
  create: (_) => Products(),
  child: HogeWidget(),
)

// value constructor
ChangeNotifierProvider.value(
  value: products[i],
  child: HogeWidget()
)

listen: falseConsumer で一部だけ更新

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../providers/product.dart';

class ProductItem extends StatelessWidget {
  @override
  Widget build(BuildContext context) {

    // 👉 `listen: false`でrebuildしないようにする
    final product = Provider.of<Product>(context, listen: false);

    return GridTile(
      child: GestureDetector(
        child: Image.network(product.imageUrl, fit: BoxFit.cover),
        onTap: () {
          Navigator.of(context).pushNamed(ProductDetailScreen.routeName,
              arguments: product.id);
        },
      ),
      footer: GridTileBar(
        leading: IconButton(
          onPressed: product.toggleFavoriteStatus,
          icon: Consumer<Product>(
            // 👉 でもConsumerの中だけは更新されるよ。
            builder: (ctx, product, child) => Icon(
              product.isFavorite ? Icons.favorite : Icons.favorite_border,
            ),
          ),
        ),
        title: Text(product.title),
      ),
    );
  }
}

複数のProviderをまとめて登録

Widget build(BuildContext context) {
  return MultiProvider(
    providers: [
      ChangeNotifierProvider(
        create: (_) => Products(),
      ),
      ChangeNotifierProvider(
        create: (_) => Cart(),
      ),
    ],
    child: HogeWeidget(),
  );

FutureBuilder

  • Future (async fucntion) の結果に応じてBuild内容を変更できるよ。
  • Statelessで使える。
FutureBuilder(
  future: Provider.of<Orders>(context, listen: false).fetchAndSetOrders(),
  builder: (ctx, dataSnapshot) {
    if (dataSnapshot.connectionState == ConnectionState.waiting) {
      // Show loading...
      return Center(child: CircularProgressIndicator());
    } else {
      if (dataSnapshot.error != null) {
        // Do error handling stuff...
      } else {
        return Consumer<Orders>(
          builder: (ctx, orderData, child) => ListView.builder(
                itemCount: orderData.orders.length,
                itemBuilder: (ctx, i) => OrderItem(orderData.orders[i]),
              ),
        );
      }
    }
  },
);

その他の便利なスニペット

async/await で sleep

await Future.delayed(const Duration(seconds: 10));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment