Flutter Crash Note

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →

Cheat Sheet

  • ?? if value of the left is null than return the vlaue from the right

    EX: A ?? false if A is null than return false

  • final A value that can be assigned only once

ListWheelScrollView.useDelegat

Container(
    height: 100,
    width: 100,
    child: ListWheelScrollView.useDelegate(
        physics: FixedExtentScrollPhysics(),
        onSelectedItemChanged: (index) {
          // set value here
        },
        itemExtent: 50,
        childDelegate: ListWheelChildBuilderDelegate(
            builder: (current, index) {
          return index >= 0 && index <= 50
              ? Center(
                  child: Text("$index"),
                )
              : null;
            }
        )
    )
)

Animation


Method Channel

Android Platform

List

  • List.filled( <how many length> , <value to fill in> ) to fill the length of value to the list

StateFulBuilder

StatefulBuilder(
  builder: (BuildContext context, StateSetter setState) {
    bool isLightOn = false; // Local state for the switch
    return Column(
      mainAxisSize: MainAxisSize.min,
      children: [
        Text(
          isLightOn ? "Light is ON" : "Light is OFF",
          style: TextStyle(fontSize: 20),
        ),
        SizedBox(height: 20),
        ElevatedButton(
          onPressed: () {
            setState(() {
              isLightOn = !isLightOn; // Toggle the state
            });
          },
          child: Text("Toggle Light"),
        ),
      ],
    );
  },
)

Scroll list picker

Container(
    color: Colors.greenAccent,
    height: 100,
    width: 100,
    child: ListWheelScrollView.useDelegate(
      itemExtent: 50,
      perspective: 0.005,
      diameterRatio: 1.5,
      physics: FixedExtentScrollPhysics(),
      onSelectedItemChanged: (index) {
        setState(() {
          // selectedMinutes = index;
        });
      },
      childDelegate: ListWheelChildBuilderDelegate(
        builder: (context, index) {
          if (index >= 0 && index <= 60) {
            return Center(
              child: Text(
                index.toString().padLeft(2, '0'),
                style: TextStyle(fontSize: 25),
              ),
            );
          }
          return null;
        },
        childCount: 61,
      ),
    )
)

Timer

  • Like tick in windows form, run a task base on a fixed time

Basic Operation

Start

Create a value for storing countdown time, a late init variable with type Timer for storing Timer and a variable for checking a timer is running

var _time = 60;
late Timer timer;
var timerStarted = false;

Start a Timer

void start() {
    if (!timerStarted)
      timerStarted = true;
    else
      return;
    timer = Timer.periodic(Duration(seconds: 1), (time) {
      setState(() {
        if (_time > 0) {
          _time--;
        } else {
          timer.cancel();
          timerStarted = false;
        }
      });
    });
}

Pause the timer

void pause(){
    timer.cancel();
    timerStarted = false;
}

Paint

  • All the drawing stuff in a class that extend CustomPainter
  • paint function contain the main logic of drawing
  • shouldRepaint function is for setting whether the paint should be update return true false or a logic
class Artist extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    final paint = Paint()
      ..color = Colors.red
      ..strokeWidth = 10
      ..style = PaintingStyle.stroke;

  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) {
    return true;
  }
}

Style

Padding

Padding(
    padding: const EdgeInsets.only(left: 10, bottom: 10),
    child: Placeholder()
)

Border Radius

  • With BoxDecoration
Container(
  decoration: BoxDecoration(
    color: Colors.blue,
    borderRadius: BorderRadius.circular(10.0),
  ),
  child: Placeholder(),
)
  • Custom Radius
borderRadius: BorderRadius.only(
    topLeft: Radius.circular(20.0),
    topRight: Radius.circular(15.0),
    bottomLeft: Radius.circular(10.0),
    bottomRight: Radius.circular(5.0),
)
  • For all single widget
ClipRRect(
    borderRadius: BorderRadius.circular(10),
    child: Image(
        image: NetworkImage("https://eliaschen.dev/eliaschen.jpg"),
    ),
)

Shadow

  • With BoxDecoration
    image
boxShadow: [
    BoxShadow(
        color: Colors.grey.withOpacity(0.5),
        spreadRadius: 1,
        blurRadius: 10,
        offset: Offset(0, 5),
    )
]

image

Container(
    decoration: BoxDecoration(
      borderRadius: BorderRadius.circular(10),
      boxShadow: [
        BoxShadow(
          color: Colors.black12,
          offset: Offset(0.5, 0.5),
          blurRadius: 5,
          spreadRadius: 0.5,
        ),
        BoxShadow(
          color: Colors.white,
          offset: const Offset(0.0, 0.0),
          blurRadius: 0.0,
          spreadRadius: 0.0,
        ),
      ],
    ),
    child: Padding(
      padding: const EdgeInsets.all(20),
      child: Text("data"),
    ),
)

Expanded

  • Must be used in Row,Flex

Widgets

ElevatedButton

ElevatedButton(
    style: ElevatedButton.styleFrom(
        backgroundColor: WidgetStateProperty.all(Colors.grey),
        foregroundColor: WidgetStateProperty.all(Colors.black),
        shape: WidgetStateProperty.all(
            RoundedRectangleBorder(
                borderRadius: BorderRadius.circular(10)
            )
        )
    )
)

Animation

TweenAnimationBuilder

TweenAnimationBuilder(
    tween: Tween(
        begin: 1.0,
        end: originalTime > 0
            ? currentTime / originalTime
            : 0.0),
    duration: Duration(seconds: 1),
    builder: (BuildContext context, double value,
        Widget? child) {
      return CircularProgressIndicator(
        value: value,
        strokeWidth: 10,
      );
    },
)

ReorderableList

SizedBox(
  width: double.infinity,
  height: 500,
  child: ReorderableListView(
    onReorder: (int oldIndex, int newIndex) {
      setState(() {
        if (newIndex > oldIndex) {
          newIndex -= 1;
        }
        final item = data.removeAt(oldIndex);
        data.insert(newIndex, item);
      });
    },
    children: data.map((item) {
      return ListTile(
        shape: RoundedRectangleBorder(
          borderRadius: BorderRadius.circular(20),
        ),
        key: ValueKey(item['id']),
        title: Text(item['name']),
        subtitle: Text(item['type'].toString().toUpperCase()),
        leading: Text(TimeFormatter(item['time'])),
        trailing: const Icon(Icons.drag_indicator),
      );
    }).toList(),
  ),
)

DraggableScrollableSheet

Remember to put the DraggableScrollableSheet into a Stack widget to let it float on top other widget

DraggableScrollableSheet(
  initialChildSize: 0.15,
  minChildSize: 0.15,
  maxChildSize: 1,
  builder: (context, scroller) {
    return Container(
      decoration: const BoxDecoration(
        color: Colors.white,
      ),
      child: SingleChildScrollView(
        controller: scroller,
      ),
    );
  },
);

image

var workType = TextEditingController();

DropdownMenu(
    label: Text("Chose the work type"),
    width: 200,
    initialSelection: 0,
    controller: workType,
    dropdownMenuEntries: [
        DropdownMenuEntry(value: 0, label: "Rest"),
        DropdownMenuEntry(value: 1, label: "Work"),
    ]
)

TextField

use a textController to make the inputfield auto update the value in the input box when the value change

  • create a text controller then use the <controller-name>.text to get the value
var email = TextEditingController();
TextField(
  controller: password,
  obscureText: hidePassword,
  obscuringCharacter: '*',
  decoration: InputDecoration(
    labelText: "主密碼",
    suffixIcon: IconButton(
      icon: Icon(
        hidePassword ? Icons.visibility : Icons.visibility_off,
      ),
      onPressed: () {
        setState(() {
          hidePassword = !hidePassword;
        });
      },
    ),
  ),
)

PopupMenuButton

PopupMenuButton(
      itemBuilder: (BuildContext context) => <PopupMenuEntry>[
        const PopupMenuItem(
          child: Text('Option1'),
        ),
        const PopupMenuItem(
          child: Text('Option2'),
        ),
        const PopupMenuItem(
          child: Text('Option3'),
        ),
    ],
)

SnackBar

    ScaffoldMessenger.of(context)
        .showSnackBar(
        SnackBar(
            duration: Duration(seconds: 2),
            content: Text("登入成功")
        )
    );

Dismissible

Dismissible(
    key: ValueKey(<a-string-that-is-static>),
    onDissmissed: (direction){
        if(direction == DismissDirection.endToStart){
            // when swipe right to left
        }else if(direction == DismissDirection.startToEnd){
            // when swipe left to right   
        }else if(direction == DismissDirection.horizontal){
            // when swipe both direction       
        }
    }
)

Copy to Clipboard

Clipboard.setData(ClipboardData(text: password))

Scaffold

Scaffold(
      appBar: AppBar(
        title: Text('Playground'),
        actions: [
          // Right side
        ],
      ),
      drawer: Drawer(
        child: Placeholder(),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {},
        child: Icon(Icons.add),
      ),
      body: Placeholder(),
)

PopupMenuItem

PopupMenuButton(
          itemBuilder: (context) => [
                PopupMenuItem(
                  onTap: () {},
                  child: Placeholder()
                ),                
                PopupMenuItem(
                  onTap: () {},
                  child: Placeholder()
                ),
					]
)

Dialog

All the dialog use pop back to dismiss

Dialog

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →
ElevatedButton(
    onPressed: () => showDialog(
        context: context,
        builder: (context) => Dialog(
              child: Column(
                children: [
                  Text("Hello Dialog"),
                ],
              ),
            )),
    child: Text("Show Dialog")
)

Alert dialog

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →
ElevatedButton(
    onPressed: () => showDialog(
          context: context,
          builder: (context) => AlertDialog(
            title: Text("AlertDialog"),
            content: Text(
              "hello there",
            ),
            actions: [
              TextButton(
                  onPressed: () {}, child: Text("Close")),
              TextButton(onPressed: () {}, child: Text("Ok"))
            ],
          ),
        ),
    child: Text("Show AlertDialog")
),

FullScreen dialog

Image Not Showing Possible Reasons
  • The image was uploaded to a note which you don't have access to
  • The note which the image was originally uploaded to has been deleted
Learn More →
ElevatedButton(
    onPressed: () => showDialog(
        context: context,
        builder: (BuildContext context) => Dialog.fullscreen(
              child: Column(
                children: [
                  Text("Hello FullScreen Dialog"),
                ],
              ),
            )),
    child: Text("Show FullScreen Dialog"),
)

Navigator

  • Nav to a widget (make it async to handle task after popBack)
Navigator.push(context,
	MaterialPageRoute(builder: (context) => YourDestinationScreen())
);
  • Go back
Navigator.pop(context);

Delay

await Future.delayed(const Duration(seconds: 2));
  • Duration support unit days horus minutes seconds milliseconds microseconds

Shaking

Raw Shaking Detection

  • Android Layer
    In MainActivity.kt create a methodChannel to retrive event in flutter layer
    ​​​​package dev.eliaschen.re_flutter_skills_pre
    
    ​​​​import android.hardware.*
    ​​​​import android.os.Bundle
    ​​​​import io.flutter.embedding.android.FlutterActivity
    ​​​​import io.flutter.embedding.engine.FlutterEngine
    ​​​​import io.flutter.plugin.common.MethodChannel
    ​​​​import kotlin.math.sqrt
    
    ​​​​class MainActivity : FlutterActivity(), SensorEventListener {
    ​​​​    private lateinit var sensorManager: SensorManager
    ​​​​    private var methodChannel: MethodChannel? = null
    ​​​​    private var lastShakeTime = 0L
    
    ​​​​    override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
    ​​​​        super.configureFlutterEngine(flutterEngine)
    ​​​​        methodChannel = MethodChannel(flutterEngine.dartExecutor.binaryMessenger, "shake_detector")
    ​​​​    }
    
    ​​​​    override fun onCreate(savedInstanceState: Bundle?) {
    ​​​​        super.onCreate(savedInstanceState)
    ​​​​        sensorManager = getSystemService(SENSOR_SERVICE) as SensorManager
    ​​​​        sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)?.also {
    ​​​​            sensorManager.registerListener(this, it, SensorManager.SENSOR_DELAY_UI)
    ​​​​        }
    ​​​​    }
    
    ​​​​    override fun onSensorChanged(event: SensorEvent?) {
    ​​​​        event?.values?.let {
    ​​​​            if (sqrt((it[0] * it[0] + it[1] * it[1] + it[2] * it[2]).toDouble()) / 9.81 > 2.7) {
    ​​​​                val now = System.currentTimeMillis()
    ​​​​                if (now - lastShakeTime > 500) {
    ​​​​                    lastShakeTime = now
    ​​​​                    methodChannel?.invokeMethod("onShake", null)
    ​​​​                }
    ​​​​            }
    ​​​​        }
    ​​​​    }
    
    ​​​​    override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {}
    ​​​​}
    
    
  • Flutter Layer
    use ShakeDetector.startListening(); to excute a function when the phone shake
    ​​​​import 'package:flutter/services.dart';
    
    ​​​​class ShakeDetector {
    ​​​​  static const platform = MethodChannel('shake_detector');
    
    ​​​​  static void startListening(Function onShake) {
    ​​​​    platform.setMethodCallHandler((call) async {
    ​​​​      if (call.method == "onShake") {
    ​​​​        onShake();
    ​​​​      }
    ​​​​    });
    ​​​​  }
    ​​​​}
    
    Call the function when the phone shake
    ​​​​@override
    ​​​​void initState() {
    ​​​​    super.initState();
    ​​​​    ShakeDetector.startListening(() {
    ​​​​         ScaffoldMessenger.of(context)
    ​​​​            .showSnackBar(SnackBar(content: Text("the phone shake")));
    ​​​​    });
    ​​​​}
    

DateTime

  • .now() get the current time
  • .parse() parsing from ISO 8601
  • .fromMillisecondsSinceEpoch() parsing from UNIX TimeStamp
DateTime fromISO = DateTime.parse(<iso-time-string>);
DateTime fromTimeStamp = DateTime.parse(<UNIX-TimeStamp>);

Network Request

GET

Future<void> fetchData() async{
    final url = Uri.parse('<API-endpoint>');
    final httpClient = HttpClient();
    try {
        final request = await httpClient.getUrl(url);
        final response = await request.close(); // Send the request

        if (response.statusCode == HttpStatus.ok) {
            final res = await response.transform(utf8.decoder).join();
            final jsonData = jsondecode(res);
            /* handle return */
        }
    } catch (e) {
        /* handle error */
    } finally {
        httpClient.close();
    }
}
  • add haders
    Just use request.headers.add() to add header to the request
request.headers.add("search","na");

Headers as a list

Create a Map for storing headers

Map<String, dynamic> body = {
    'title': 'Flutter POST Request',
    'body': 'This is a test post',
    'userId': 1,
};

Then add body to the request

request.add(utf8.encode(jsonEncode(body)));

POST

basically same as the GET request, just replace httpClient.getUrl(url) to httpClient.postUrl(url)

Json parsing

  • the json the extract method is kind of like python, use [ ]instead of . in Javascript
  • Ex: assets/data.json
{
  "result": {
    "offset": 0,
    "limit": 10000,
    "count": 1,
    "sort": "",
    "results": [
      {
        "_id": 1,
        "Station": "後山埤站",
        "Destination": "頂埔站",
        "UpdateTime": "2015-08-24T12:03:27.907"
      },
      {
        "_id": 2,
        "Station": "永寧站",
        "Destination": "南港展覽館站",
        "UpdateTime": "2015-08-24T12:03:27.907"
      },
      {
        "_id": 3,
        "Station": "台北車站",
        "Destination": "南港展覽館站",
        "UpdateTime": "2015-08-24T12:03:37.5"
      }
    ]
  }
}
  • from assets
String jsonString = await rootBundle.loadString('assets/data.json');
final List<dynamic> jsonResponse = jsonDecode(jsonString)["results"][""];
  • from app dir
final directory = await getApplicationDocumentsDirectory();
final filePath = '${directory.path}/$filename';

final file = File(filePath);
final jsonString = await file.readAsString();
final List<dynamic> jsonData = jsonDecode(jsonString);

TypeSafe (with class & Future Builder)

Create class for data parsing & typesafe

class StationSchema {
  final int id;
  final String Station;
  final String Destination;
  final String UpdateTime;

  StationSchema({
    required this.id,
    required this.Station,
    required this.Destination,
    required this.UpdateTime,
  });

  factory StationSchema.fromMap(Map<String, dynamic> map) {
    return StationSchema(
      id: map['_id'],
      Station: map['Station'],
      Destination: map['Destination'],
      UpdateTime: map['UpdateTime'],
    );
  }
}

Use fromMap to convert Map format to the stander list in dart

Future<List<StationSchema>> parseJson() async {
    String jsonString = await rootBundle.loadString('assets/data.json');
    List<dynamic> jsonMap = jsonDecode(jsonString)[0]['result']['results'];
    List<StationSchema> stations = jsonMap.map((json) => StationSchema.fromMap(json)).toList();
    await Future.delayed(Duration(seconds: 2));
    return stations;
}

Future Builder

  • using snapshot.data to get the data of the list we do json parsing before
FutureBuilder<List<StationSchema>>(
    key: ValueKey<int>(key),
    future: parseJson(),
    builder: (context, snapshot) {
        // something like ListView in there
        
    },
)

Sorting Map & List

Sqflite

Dependencies

  • sqflite
  • path_provider

Create a Schema

class TodoSchema {
  final String todo;
  final int done;
  final int? id;

  TodoSchema({this.id, required this.todo, required this.done});

  Map<String, dynamic> toMap() {
    return {'id': id, 'todo': todo, 'done': done};
  }
}

Low level syntax

rawQuery excute
return Future<List<Map<String, dynamic>>> Don’t have a retrun value
require await require await
can be save to List<Map<String, dynamic>> use ? to set the value later in []
List<Map<String,dynamic>> data = await <db>.rawQuery('<sql-statement>', [ ]) await <db>.excute('<sql-statement>', [ ])

CRUD operation example

class Appdb {
  static Database? db;

  static Future<Database> getDbConnection() async {
    db ??= await initDatabase(); // if db is null, initialize it
    return db!; // return db if it is not null
  }

  static Future<List<TodoSchema>> getTodos() async {
    final Database db = await getDbConnection();
    final List<Map<String, dynamic>> maps = await db
        .query('todos ORDER BY id DESC'); // get all todos in descending order
    return List.generate(maps.length, (index) {
      return TodoSchema(
        id: maps[index]['id'],
        todo: maps[index]['todo'],
        done: maps[index]['done'],
      );
    });
  }

  static Future<void> addTodo(TodoSchema todo) async {
    final Database db = await getDbConnection();
    await db.insert(
      'todos',
      todo.toMap(),
      conflictAlgorithm: ConflictAlgorithm.replace,
    );
  }

  static Future<void> deleteTodo(int id) async {
    final Database db = await getDbConnection();
    await db.delete(
      'todos',
      where: 'id = ?',
      whereArgs: [id],
    );
  }

  static Future<void> deleteAllTodos() async {
    final Database db = await getDbConnection();
    await db.delete('todos');
  }

  static Future<void> updateTodo(int id, int done) async {
    final Database db = await getDbConnection();
    await db.update(
      'todos',
      {'done': done},
      where: 'id = ?',
      whereArgs: [id],
    );
  }

  static Future<Database> initDatabase() async {
    var databasesPath = await getApplicationDocumentsDirectory(); // IMPORTANT
    String path = join(databasesPath.path, 'todoapp.db');
    return await openDatabase(path, version: 1,
        onCreate: (Database db, int version) async {
      await db.execute(
          'CREATE TABLE todos(id INTEGER PRIMARY KEY, todo TEXT, done INTEGER)');
    });
  }
}