Tag: flutter

  • 關於 Flutter 的 FutureBuilder

    關於 Flutter 的 FutureBuilder

    在使用Flutter進行開發時,我們經常會遇到需要處理異步操作的情況,比如從網路上獲取數據或進行耗時的運算。為了在使用者介面上呈現異步操作的結果,Flutter提供了一個名為FutureBuilder的Widget。FutureBuilder是一個功能強大且靈活的工具,它能幫助我們簡化異步操作的處理,並能根據操作的狀態動態地構建使用者介面。

    FutureBuilder是一個Widget,其功能是根據異步操作的狀態動態地構建使用者介面。它會接收一個Future物件作為輸入,並根據Future的不同狀態(未完成、完成、錯誤)來構建不同的使用者介面。FutureBuilder能夠將異步操作的結果直接用於構建使用者介面,無需手動管理狀態或訂閱事件。

    FutureBuilder的運作方式相當直觀,它透過接收一個Future物件並監聽其狀態的變化來構建使用者介面。根據Future的狀態,FutureBuilder會調用不同的回調函數來構建對應的使用者介面:

    1. 當Future還未完成時,FutureBuilder會調用builder回調函數,並傳遞一個BuildContext和AsyncSnapshot物件。我們可以根據AsyncSnapshot的狀態來構建正在加載的使用者介面。
    2. 當Future成功完成時,FutureBuilder會調用builder回調函數,並傳遞一個BuildContext和AsyncSnapshot物件。我們可以根據AsyncSnapshot的狀態來構建成功完成時的使用者介面,並使用AsyncSnapshot的data屬性來訪問Future的結果數據。
    3. 當Future發生錯誤時,FutureBuilder會調用errorBuilder回調函數,並傳遞一個BuildContext和錯誤物件。我們可以根據錯誤物件來構建錯誤提示的使用者介面。

    使用情境

    獲取網路數據並構建使用者介面:FutureBuilder非常適合於從網路獲取數據並將其用於構建使用者介面的情境。我們可以在FutureBuilder的future參數中傳遞一個異步的網路請求,並根據請求的結果來構建不同的使用者介面,例如顯示正在加載的使用者介面、成功獲取數據後的使用者介面或錯誤提示的使用者介面。

    進行異步計算和處理:除了獲取網路數據,FutureBuilder也適用於執行耗時的計算或其他異步操作,並根據操作結果來構建使用者介面。我們可以在future參數中傳遞一個異步的計算函數,並根據計算結果來構建相應的使用者介面。

    與其他Widget結合使用:FutureBuilder可以與其他小部件組合使用,以實現更複雜的異步操作和使用者介面交互。例如,我們可以將FutureBuilder嵌入在ListView或GridView中,以構建一個根據異步數據生成的動態列表的使用者介面。

    使用時的注意事項

    避免頻繁重建使用者介面:由於FutureBuilder會根據異步操作的狀態動態地構建使用者介面,因此在每次狀態變化時都會觸發使用者介面的重建。為了避免不必要的重建,我們可以使用const關鍵字來標記不需要重新構建的部分,或者將FutureBuilder封裝在StatefulWidget中,通過維護狀態來控制使用者介面的重建。 錯誤處理和錯誤信息展示:FutureBuilder提供了errorBuilder回調函數,用於處理Future發生錯誤的情況。在構建錯誤提示的使用者介面時,我們應該提供有意義的錯誤信息,並考慮使用者體驗和友善的錯誤處理方式。

    替代方案

    雖然FutureBuilder是處理異步操作和構建使用者介面的常見工具,但在特定情況下,你可能會考慮使用其他的替代方案。以下是一些常見的替代方案:

    StreamBuilder:如果你的異步操作返回的是一個數據流(Stream),而不只是一個Future,那麼可以使用StreamBuilder來處理。StreamBuilder與FutureBuilder相似,但用於處理數據流的更新。它監聽數據流的狀態變化,並根據流的狀態構建相應的使用者介面。 Provider和ChangeNotifier:如果你需要在異步操作中更新應用程式的狀態,並通知使用者介面進行相應的更新,可以使用Provider和ChangeNotifier。Provider是Flutter生態中的狀態管理工具,而ChangeNotifier是一個可觀察的模型,用於追蹤狀態的變化。你可以使用Future或其他異步操作來更新ChangeNotifier,並通過Provider將狀態提供給使用者介面組件。 StatefulWidget和State:如果你需要更細緻地控制異步操作和使用者介面的交互,可以使用StatefulWidget和State。你可以創建一個帶有狀態的小部件,將異步操作封裝在狀態中,並在狀態的生命週期方法中處理異步操作和使用者介面更新。 Reactive框架:在Flutter生態系統中,還有一些基於響應式編程的框架,例如ReactiveX和RxDart。這些框架提供了豐富的操作符和轉換器,用於處理異步操作和數據流。如果你對響應式編程有一定的了解,並且需要處理複雜的異步操作和數據流轉換,可以考慮使用這些框架。

    需要注意的是,這些替代方案並不是FutureBuilder的直接替代品,而是根據具體的需求和情況選擇合適的工具和模式。FutureBuilder在處理簡單的異步操作和構建基本使用者介面時非常方便,但在複雜的場景下,其他方案可能更適合。根據你的項目需求和個人偏好,選擇最適合的工具和模式來處理異步操作和構建使用者介面。

  • Flutter Web Service Client Sample 範例 (續) – 自動儲存和載入最後一次查詢條件

    Flutter Web Service Client Sample 範例 (續) – 自動儲存和載入最後一次查詢條件

    承前一天的查詢方塊功能,透過 SharedPreferences 套件儲存和載入查詢的條件。讓 App開啟時自動載入最後一次的查詢條件。(此機制常用於儲存使用者的登入帳號密碼並自動登入)

    daos/shared_preferences_dao.dart

    import 'package:shared_preferences/shared_preferences.dart';

    class SharedPreferencesDao {
    static SharedPreferences? _prefs;

    static void initialize() async {
    _prefs ??= await SharedPreferences.getInstance();
    }

    static String getSearchKey() {
    String? searchKey;
    if(_prefs != null) {
    searchKey = _prefs!.getString("searchKey");
    }
    searchKey ??= '';

    return searchKey;
    }

    static void setSearchKey(String searchKey) {
    if(_prefs != null) {
    _prefs!.setString("searchKey", searchKey);
    }
    }
    }

    daos/post_dao.dart

    import 'package:webservice_client/models/post.dart';
    import 'package:http/http.dart' as http;
    import 'package:webservice_client/daos/shared_preferences_dao.dart';
    import 'dart:convert';

    class PostDao {
    static Future<List<Post>> getPostsAndInitializeSharedPreferences() async {
    SharedPreferencesDao.initialize();

    var url = Uri.parse('https://jsonplaceholder.typicode.com/posts');
    var response = await http.get(url);

    List<Post> posts = (jsonDecode(response.body) as List<dynamic>).map((jsonObject) {
    return Post.fromJson(jsonObject);
    }).toList();

    return posts;
    }
    }

    components/post_table_with_search.dart

    import 'dart:convert';
    import 'package:flutter/material.dart';
    import 'package:webservice_client/daos/shared_preferences_dao.dart';
    import 'package:webservice_client/models/post.dart';

    // ignore: must_be_immutable
    class PostTableWithSearch extends StatefulWidget {
    PostTableWithSearch(this.posts, {super.key});
    List<Post> posts;

    @override
    State createState() {
    return _PostTableWithSearch();
    }
    }

    class _PostTableWithSearch extends State<PostTableWithSearch> {
    String filterInput = '';
    List<Post> filteredPosts = [];

    void changeFilteredPosts(String userInput) {
    filterInput = userInput;
    filteredPosts = widget.posts.where(
    (element) {
    if(userInput == '') {
    return true;
    }
    else if(element.title.contains(userInput)) {
    //print(element.title);
    return true;
    }
    else {
    return false;
    }
    },
    ).toList();

    if(filteredPosts.isEmpty) {
    filteredPosts.add(Post(0, 0, "查無資料", "查無資料"));
    }

    SharedPreferencesDao.setSearchKey(userInput);
    }

    @override
    void initState() {
    super.initState();

    filterInput = SharedPreferencesDao.getSearchKey();
    }

    @override
    Widget build(BuildContext context) {

    if(filteredPosts.isEmpty) {
    changeFilteredPosts(filterInput);
    }

    var searchTextEditingController = TextEditingController()..text = filterInput;

    Widget searchBar = TextField(
    controller: searchTextEditingController,
    decoration: const InputDecoration(
    border: OutlineInputBorder(),
    hintText: 'Enter a search term'
    ),
    onSubmitted: (userInput){
    setState(() {
    changeFilteredPosts(userInput);
    });
    },
    );

    // Convert object variables to data column list
    List<String> columnNames = (jsonDecode(filteredPosts[0].toJsonObjectString()) as Map<String, dynamic>).keys.toList();
    List<DataColumn> dataColumns = columnNames.map((key) {
    return DataColumn(label: Text(key));
    },).toList();

    // Convert object variables to data row list
    List<DataRow> dataRows = filteredPosts.map((post) {
    Map<String, dynamic> postJson = jsonDecode(post.toJsonObjectString()) as Map<String, dynamic>;
    List<DataCell> dataCells = columnNames.map((key){
    return DataCell(
    Text(postJson[key].toString()));
    }).toList();

    return DataRow(cells: dataCells);
    },).toList();

    return SingleChildScrollView(
    child: Container(
    alignment: Alignment.topCenter,
    child: Column(children: [
    SizedBox(
    width: 800,
    child: searchBar,
    ),
    SizedBox(
    width: 800,
    child: DataTable(columns: dataColumns, rows: dataRows),
    )
    ]),
    ),
    );
    }
    }

    screens/post_screen.dart

    import 'package:flutter/material.dart';
    import 'package:webservice_client/components/post_table_with_search.dart';
    import 'package:webservice_client/daos/post_dao.dart';
    import 'package:webservice_client/models/post.dart';

    class PostScreen extends StatefulWidget {
    const PostScreen({super.key});



    @override
    State createState() {
    return _PostScreen();
    }
    }

    class _PostScreen extends State<PostScreen> {
    @override
    Widget build(BuildContext context) {
    return FutureBuilder(
    future: PostDao.getPostsAndInitializeSharedPreferences(),
    builder: (BuildContext context, AsyncSnapshot<List<Post>> asyncSnapshot) {
    List<Post> posts = [];
    if(asyncSnapshot.connectionState == ConnectionState.done) {
    posts = asyncSnapshot.requireData;

    return Scaffold(
    body: PostTableWithSearch(posts),
    );
    }
    else if(asyncSnapshot.hasError) {
    return const Scaffold(body: Text('Initial data fail!'),);
    }
    else {
    return const Scaffold();
    }
    }
    );
    }
    }

    pubspec.yaml

    name: webservice_client
    description: "A http client sample project."
    publish_to: 'none'
    version: 0.1.0+1

    environment:
    sdk: '>=3.2.6 <4.0.0'

    dependencies:
    flutter:
    sdk: flutter
    http: ^0.13.4
    shared_preferences: ^2.0.8

    dev_dependencies:
    flutter_test:
    sdk: flutter
    flutter_lints: ^2.0.0

    flutter:
    uses-material-design: true

  • Flutter Web Service Client Sample 範例 (續) – 加上查詢方塊

    Flutter Web Service Client Sample 範例 (續) – 加上查詢方塊

    針對前一個 Web Service Client 和表格呈現資料的範例,增加需要處理狀態更新機制(StatefulWidget)的查詢方塊。

    components/post_table_with_search.dart

    import 'dart:convert';
    import 'package:flutter/material.dart';
    import 'package:webservice_client/models/post.dart';

    // ignore: must_be_immutable
    class PostTableWithSearch extends StatefulWidget {
    PostTableWithSearch(this.posts, {super.key});
    List<Post> posts;

    @override
    State createState() {
    return _PostTableWithSearch();
    }
    }

    class _PostTableWithSearch extends State<PostTableWithSearch> {
    String filterInput = '';
    List<Post> filteredPosts = [];

    void changeFilteredPosts(String userInput) {
    filterInput = userInput;
    filteredPosts = widget.posts.where(
    (element) {
    if(userInput == '') {
    return true;
    }
    else if(element.title.contains(userInput)) {
    //print(element.title);
    return true;
    }
    else {
    return false;
    }
    },
    ).toList();

    if(filteredPosts.isEmpty) {
    filteredPosts.add(Post(0, 0, "查無資料", "查無資料"));
    }
    }

    @override
    Widget build(BuildContext context) {
    if(filteredPosts.isEmpty) {
    changeFilteredPosts('');
    }

    var searchTextEditingController = TextEditingController()..text = filterInput;

    Widget searchBar = TextField(
    controller: searchTextEditingController,
    decoration: const InputDecoration(
    border: OutlineInputBorder(),
    hintText: 'Enter a search term'
    ),
    onSubmitted: (userInput){
    setState(() {
    changeFilteredPosts(userInput);
    });
    },
    );

    // Convert object variables to data column list
    List<String> columnNames = (jsonDecode(filteredPosts[0].toJsonObjectString()) as Map<String, dynamic>).keys.toList();
    List<DataColumn> dataColumns = columnNames.map((key) {
    return DataColumn(label: Text(key));
    },).toList();

    // Convert object variables to data row list
    List<DataRow> dataRows = filteredPosts.map((post) {
    Map<String, dynamic> postJson = jsonDecode(post.toJsonObjectString()) as Map<String, dynamic>;
    List<DataCell> dataCells = columnNames.map((key){
    return DataCell(
    Text(postJson[key].toString()));
    }).toList();

    return DataRow(cells: dataCells);
    },).toList();

    return SingleChildScrollView(
    child: Container(
    alignment: Alignment.topCenter,
    child: Column(children: [
    SizedBox(
    width: 800,
    child: searchBar,
    ),
    SizedBox(
    width: 800,
    child: DataTable(columns: dataColumns, rows: dataRows),
    )
    ]),
    ),
    );
    }
    }

    screen/post_screen.dart

    import 'package:flutter/material.dart';
    import 'package:webservice_client/components/post_table_with_search.dart';
    import 'package:webservice_client/daos/post_dao.dart';
    import 'package:webservice_client/models/post.dart';

    class PostScreen extends StatefulWidget {
    const PostScreen({super.key});

    @override
    State createState() {
    return _PostScreen();
    }
    }

    class _PostScreen extends State<PostScreen> {
    @override
    Widget build(BuildContext context) {
    return FutureBuilder(
    future: PostDao.getPosts(),
    builder: (BuildContext context, AsyncSnapshot<List<Post>> asyncSnapshot) {
    return Scaffold(
    body: PostTableWithSearch(asyncSnapshot.requireData),
    );
    }
    );
    }
    }
  • Flutter Web Service Client Sample 範例 (續) – 用DataTable 呈現資料

    Flutter Web Service Client Sample 範例 (續) – 用DataTable 呈現資料

    原範例是直接用文字的方式呈現,下面是改成透過 DataTable 元件以表格方式呈現。
    (以下為新增和修改的 .dart 程式檔案)

    component/post_table.dart

    import 'dart:convert';
    import 'package:flutter/material.dart';
    import 'package:webservice_client/models/post.dart';

    // ignore: must_be_immutable
    class PostTable extends StatelessWidget {
    PostTable(this.posts, {super.key});

    List<Post> posts;

    @override
    Widget build(BuildContext context) {
    // Convert object variables to data column list
    List<String> columnNames = (jsonDecode(posts[0].toJsonObjectString()) as Map<String, dynamic>).keys.toList();
    List<DataColumn> dataColumns = columnNames.map((key) {
    return DataColumn(label: Text(key));
    },).toList();

    // Convert object variables to data row list
    List<DataRow> dataRows = posts.map((post) {
    Map<String, dynamic> postJson = jsonDecode(post.toJsonObjectString()) as Map<String, dynamic>;
    List<DataCell> dataCells = columnNames.map((key){
    return DataCell(
    Text(postJson[key].toString()));
    }).toList();

    return DataRow(cells: dataCells);
    },).toList();

    return DataTable(columns: dataColumns, rows: dataRows);
    }
    }

    screen/post_screen.dart

    import 'package:flutter/material.dart';
    import 'package:webservice_client/components/post_table.dart';
    import 'package:webservice_client/daos/post_dao.dart';
    import 'package:webservice_client/models/post.dart';

    class PostScreen extends StatelessWidget {
    const PostScreen({super.key});

    @override
    Widget build(BuildContext context) {
    return FutureBuilder(
    future: PostDao.getPosts(),
    builder: (BuildContext context, AsyncSnapshot<List<Post>> asyncSnapshot) {
    return Scaffold(
    body: PostTable(asyncSnapshot.requireData),
    );
    }
    );
    }
    }
  • Flutter Web Service Client Sample 範例

    Flutter Web Service Client Sample 範例

    使用 FutureBuilder 搭配事先定義好的對應物件,透過非同步 http 的方式接收網路上的 Json 資料。

    main.dart

    import 'package:flutter/material.dart';
    import 'package:webservice_client/screens/post_screen.dart';

    void main() {
    runApp(const AppEntryPoint());
    }

    class AppEntryPoint extends StatelessWidget {
    const AppEntryPoint({super.key});

    @override
    Widget build(BuildContext context) {
    return MaterialApp(
    routes: {
    "/post-screen":(context) => const PostScreen()
    },
    initialRoute: "/post-screen",
    );
    }
    }

    screen/post_screen.dart

    import 'package:flutter/material.dart';
    import 'package:webservice_client/components/post_text.dart';

    class PostScreen extends StatelessWidget {
    const PostScreen({super.key});

    @override
    Widget build(BuildContext context) {
    return const Scaffold(
    body: PostText(),
    );
    }
    }

    components/post_text.dart

    import 'package:flutter/material.dart';
    import 'package:webservice_client/daos/post_dao.dart';
    import 'package:webservice_client/models/post.dart';

    class PostText extends StatelessWidget {
    const PostText({super.key});

    @override
    Widget build(BuildContext context) {
    return FutureBuilder(
    future: PostDao.getPosts(),
    builder: (BuildContext context, AsyncSnapshot<List<Post>> asyncSnapshot) {
    List<Widget> widgets = [];
    //print(asyncSnapshot.connectionState);
    //print(asyncSnapshot.hasData);
    if(asyncSnapshot.connectionState == ConnectionState.done) {
    widgets = asyncSnapshot.requireData.map((post){
    return Text(post.toJsonObjectString());
    }).toList();
    }

    return SingleChildScrollView(
    child: Column(children: widgets),
    );
    },
    );
    }
    }

    daos/post_dao.dart

    import 'package:webservice_client/models/post.dart';
    import 'package:http/http.dart' as http;
    import 'dart:convert';

    class PostDao {
    static Future<List<Post>> getPosts() async {
    var url = Uri.parse('https://jsonplaceholder.typicode.com/posts');
    var response = await http.get(url);

    List<Post> posts = (jsonDecode(response.body) as List<dynamic>).map((jsonObject) {
    return Post.fromJson(jsonObject);
    }).toList();

    return posts;
    }
    }

    models/post.dart

    import 'dart:convert';

    class Post {
    int userId;
    int id;
    String title;
    String body;

    Post(this.userId, this.id, this.title, this.body);

    String toJsonObjectString(){
    return jsonEncode({
    "userId": userId,
    "id": id,
    "title": title,
    "body": body
    });
    }

    factory Post.fromJson(dynamic jsonObject) {
    return Post(
    jsonObject['userId'],
    jsonObject['id'],
    jsonObject['title'],
    jsonObject['body'],
    );
    }
    }
  • 常見的程式專案資料夾結構

    常見的程式專案資料夾結構

    下面為常見的專案資料夾結構,不僅限於 Flutter 也可適用於其他程式的開發。由小而大排列順序如下:

    1. models 資料模板
    2. dao 資料存取
    3. services 資料加工 (沒有UI)
    4. component 資料渲染組件化 (有UI)
    5. screens 組件拼湊至頁面內
  • Flutter Http Client Sample 範例

    Flutter Http Client Sample 範例

    使用 FutureBuilder 透過非同步 http 的方式 取用網路上的資源。

    main.dart

    import 'package:flutter/material.dart';
    
    import 'package:http_client_sample/screens/future_builder_screen.dart';

    void main() {
    runApp(const AppEntryPoint());
    }

    class AppEntryPoint extends StatelessWidget {
    const AppEntryPoint({super.key});

    @override
    Widget build(BuildContext context) {
    return MaterialApp(
    routes: {
    "/remote-data":(context) => const FutureBuilderScreen()
    },
    initialRoute: "/remote-data",
    );
    }
    }

    screens\future_builder_screen.dart

    import 'package:flutter/material.dart';
    
    import 'package:http/http.dart' as http;

    class FutureBuilderScreen extends StatelessWidget {
    const FutureBuilderScreen({super.key});

    Future<dynamic> getDataFromRemote() async {
    var url = Uri.parse('https://jsonplaceholder.typicode.com/posts'); // use get method
    var response = await http.get(url);
    return response.body;
    }

    @override
    Widget build(BuildContext context) {
    return FutureBuilder(
    future: getDataFromRemote(),
    initialData: const [],
    builder: (BuildContext context, AsyncSnapshot<dynamic> asyncSnapshot) {
    return Scaffold (
    body: Text(asyncSnapshot.data.toString()),);
    }
    );
    }
    }

    pubspec.yaml

  • Flutter pubspec.yaml 參數說明

    Flutter pubspec.yaml 參數說明

    • name: 程式包的名稱,一律小寫、不能有空白(用底線替代)
    • description: 程式的詳細描述
    • version: 程式版本,使用語意化版本格式(semantic versioning)。格式為:
      {主版號}.{次版號}.{修訂號}+{建置流水號}
    • sdk: Flutter sdk 版本,目前的版本為 3.2.6(含) 到 3.x.x
    • dependencies: 各種程式專案相依的程式包,版本號的部分通常會用指定主版次的方式,以獲得較佳的穩定性
      • http: ^0.13.4 就是限定 http 版本為 0.13.4(含) 到 0.x.x
      • 如果一律使用最新版本則用 any 表示,如:http: any
  • Flutter 範例專案 Caculator 計算機 App

    Flutter 範例專案 Caculator 計算機 App

    A Caculator App project for demonstrating Flutter.

    main.dart

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

    import 'package:caculator/daos/cacu_dao.dart';
    import 'package:caculator/daos/value_viewer_dao.dart';
    import 'package:caculator/screens/caculate_screen.dart';

    void main() {
    runApp(
    MultiProvider(
    providers: [
    ChangeNotifierProvider(create: (_)=> CacuDao()),
    ChangeNotifierProvider(create: (_) => ValueViewerDao())],
    child: const AppEntryPoint()
    )
    );
    }

    class AppEntryPoint extends StatelessWidget {
    const AppEntryPoint({super.key});

    @override
    Widget build(BuildContext context) {
    return MaterialApp(
    title: 'Caculator',
    theme: ThemeData(
    primarySwatch: Colors.blueGrey,
    ),
    routes: {
    "/caculate":(BuildContext context) => CaculateScreen(),
    },
    initialRoute: "/caculate",
    );
    }
    }

    models\cacu.dart

    class Cacu {
    
    String cacuValue; // Value for caculate
    CacuType cacuType; // Caculate type

    Cacu(this.cacuValue, this.cacuType);
    }

    enum CacuType {
    none,
    addition,
    subtraction,
    multiplication,
    division,
    }

    daos\cacu_dao.dart

    import 'package:flutter/material.dart';
    

    import 'package:caculator/models/cacu.dart';

    class CacuDao extends ChangeNotifier {
    List<Cacu> cacuList = [];

    List<Cacu> getCacus() {
    return cacuList;
    }

    /// Insert value for caculate later
    void insertCacu(Cacu cacu) {
    cacuList.add(cacu);

    notifyListeners();
    }

    /// Update caculate type to value
    void clearCacu() {
    cacuList.clear();

    notifyListeners();
    }
    }

    daos\value_viewer_dao.dart

    import 'package:flutter/material.dart';
    

    class ValueViewerDao extends ChangeNotifier {
    double value = 0;

    /// Update caculate type to value
    void updateValue(double value) {
    this.value = value;

    notifyListeners();
    }
    }

    components\common_functions.dart

    import 'package:caculator/models/cacu.dart';
    

    class CommonFunctions {
    static String getCacuSign(CacuType cacuType) {
    switch (cacuType) {
    case CacuType.addition:
    return "+";
    case CacuType.subtraction:
    return "-";
    case CacuType.multiplication:
    return "x";
    case CacuType.division:
    return "÷";
    default:
    return "";
    }
    }
    }
    cacu_component.dart
    import 'package:flutter/material.dart';
    

    import 'package:caculator/models/cacu.dart';
    import 'package:caculator/components/common_functions.dart';

    // ignore: must_be_immutable
    class CacuComponent extends StatefulWidget {
    Cacu cacu;

    CacuComponent(this.cacu, {super.key});

    @override
    State createState() {
    return _CacuComponent();
    }
    }

    class _CacuComponent extends State<CacuComponent> {
    @override
    Widget build(BuildContext context) {
    Widget cacuValueText = Text(
    widget.cacu.cacuValue,
    );

    Widget cacuTypeText = Text(
    CommonFunctions.getCacuSign(widget.cacu.cacuType),
    );

    return Card(
    child: ListTile(
    title: Row(children: [
    cacuValueText,
    const SizedBox(width: 10),
    cacuTypeText,
    ]),
    ),
    );
    }
    }

    screens\caculate_screen.dart

    import 'dart:async';
    

    import 'package:caculator/components/common_functions.dart';
    import 'package:flutter/material.dart';
    import 'package:provider/provider.dart';

    import 'package:caculator/daos/cacu_dao.dart';
    import 'package:caculator/components/cacu_component.dart';
    import 'package:caculator/models/cacu.dart';

    // ignore: must_be_immutable
    class CaculateScreen extends StatefulWidget {
    CaculateScreen({super.key});

    Cacu currentCacu = Cacu('0', CacuType.none);

    @override
    State createState() {
    return _CaculateScreen();
    }
    }

    class _CaculateScreen extends State<CaculateScreen> {
    final ScrollController _scrollController = ScrollController();

    @override
    Widget build(BuildContext context) {
    Timer(const Duration(milliseconds: 500), () => _scrollController.jumpTo(_scrollController.position.maxScrollExtent));

    Widget valueViewer = Row(
    mainAxisAlignment: MainAxisAlignment.center,
    children: [
    Container(
    width: 250,
    margin: const EdgeInsets.all(15.0),
    padding: const EdgeInsets.all(3.0),
    decoration: BoxDecoration(
    border: Border.all(color: Colors.blueAccent)
    ),
    child: Text(
    widget.currentCacu.cacuValue,
    textAlign: TextAlign.right,
    style: const TextStyle(fontSize: 20),),
    ),
    Text(CommonFunctions.getCacuSign(widget.currentCacu.cacuType)),
    ],
    );

    Widget keybooards = Column(children: [
    Row(
    mainAxisAlignment: MainAxisAlignment.center,
    children: [
    SizedBox(
    width: 70.0,
    child: OutlinedButton(
    child: const Text('AC',
    overflow: TextOverflow.clip,
    maxLines: 1,
    softWrap: false,
    ),
    onPressed: (){
    setState(() {
    widget.currentCacu = Cacu('0', CacuType.none);
    context.read<CacuDao>().clearCacu();
    });
    },
    )
    ),
    const SizedBox(width: 5),
    OutlinedButton(
    child: const Text('C'),
    onPressed: (){
    setState(() {
    widget.currentCacu = Cacu('0', CacuType.none);
    });
    },
    ),
    const SizedBox(width: 5),
    OutlinedButton(
    child: const Text('±'),
    onPressed: (){
    setState(() {
    if(widget.currentCacu.cacuValue[0] == '-'){
    widget.currentCacu.cacuValue = widget.currentCacu.cacuValue.substring(1);
    }
    else {
    widget.currentCacu.cacuValue = '-${widget.currentCacu.cacuValue}';
    }
    });
    },
    ),
    const SizedBox(width: 5),
    OutlinedButton(
    child: const Text('÷'),
    onPressed: (){
    setState(() {
    widget.currentCacu.cacuType = CacuType.division;
    });
    }
    ),
    ]
    ),
    const SizedBox(height: 5),
    Row(
    mainAxisAlignment: MainAxisAlignment.center,
    children: [
    OutlinedButton(
    child: const Text('7'),
    onPressed: (){
    setState(() {
    String num = '7';
    if(widget.currentCacu.cacuType == CacuType.none) {
    if(widget.currentCacu.cacuValue == '0') {
    widget.currentCacu.cacuValue = num;
    } else {
    widget.currentCacu.cacuValue = widget.currentCacu.cacuValue + num;
    }
    }
    else {
    context.read<CacuDao>().insertCacu(widget.currentCacu);
    widget.currentCacu = Cacu(num, CacuType.none);
    }
    });
    }
    ),
    const SizedBox(width: 5),
    OutlinedButton(
    child: const Text('8'),
    onPressed: (){
    setState(() {
    String num = '8';
    if(widget.currentCacu.cacuType == CacuType.none) {
    if(widget.currentCacu.cacuValue == '0') {
    widget.currentCacu.cacuValue = num;
    } else {
    widget.currentCacu.cacuValue = widget.currentCacu.cacuValue + num;
    }
    }
    else {
    context.read<CacuDao>().insertCacu(widget.currentCacu);
    widget.currentCacu = Cacu(num, CacuType.none);
    }
    });
    }
    ),
    const SizedBox(width: 5),
    OutlinedButton(
    child: const Text('9'),
    onPressed: (){
    setState(() {
    String num = '9';
    if(widget.currentCacu.cacuType == CacuType.none) {
    if(widget.currentCacu.cacuValue == '0') {
    widget.currentCacu.cacuValue = num;
    } else {
    widget.currentCacu.cacuValue = widget.currentCacu.cacuValue + num;
    }
    }
    else {
    context.read<CacuDao>().insertCacu(widget.currentCacu);
    widget.currentCacu = Cacu('0', CacuType.none);
    }
    });
    }
    ),
    const SizedBox(width: 10),
    OutlinedButton(
    child: const Text('x'),
    onPressed: (){
    setState(() {
    widget.currentCacu.cacuType = CacuType.multiplication;
    });
    }
    ),
    ],
    ),
    const SizedBox(height: 5),
    Row(
    mainAxisAlignment: MainAxisAlignment.center,
    children: [
    OutlinedButton(
    child: const Text('4'),
    onPressed: (){
    setState(() {
    String num = '4';
    if(widget.currentCacu.cacuType == CacuType.none) {
    if(widget.currentCacu.cacuValue == '0') {
    widget.currentCacu.cacuValue = num;
    } else {
    widget.currentCacu.cacuValue = widget.currentCacu.cacuValue + num;
    }
    }
    else {
    context.read<CacuDao>().insertCacu(widget.currentCacu);
    widget.currentCacu = Cacu(num, CacuType.none);
    }
    });
    }
    ),
    const SizedBox(width: 5),
    OutlinedButton(
    child: const Text('5'),
    onPressed: (){
    setState(() {
    String num = '5';
    if(widget.currentCacu.cacuType == CacuType.none) {
    if(widget.currentCacu.cacuValue == '0') {
    widget.currentCacu.cacuValue = num;
    } else {
    widget.currentCacu.cacuValue = widget.currentCacu.cacuValue + num;
    }
    }
    else {
    context.read<CacuDao>().insertCacu(widget.currentCacu);
    widget.currentCacu = Cacu(num, CacuType.none);
    }
    });
    }
    ),
    const SizedBox(width: 5),
    OutlinedButton(
    child: const Text('6'),
    onPressed: (){
    setState(() {
    String num = '6';
    if(widget.currentCacu.cacuType == CacuType.none) {
    if(widget.currentCacu.cacuValue == '0') {
    widget.currentCacu.cacuValue = num;
    } else {
    widget.currentCacu.cacuValue = widget.currentCacu.cacuValue + num;
    }
    }
    else {
    context.read<CacuDao>().insertCacu(widget.currentCacu);
    widget.currentCacu = Cacu(num, CacuType.none);
    }
    });
    }
    ),
    const SizedBox(width: 10),
    OutlinedButton(
    child: const Text('-'),
    onPressed: (){
    setState(() {
    widget.currentCacu.cacuType = CacuType.subtraction;
    });
    }
    ),
    ],
    ),
    const SizedBox(height: 5),
    Row(
    mainAxisAlignment: MainAxisAlignment.center,
    children: [
    OutlinedButton(
    child: const Text('1'),
    onPressed: (){
    setState(() {
    String num = '1';
    if(widget.currentCacu.cacuType == CacuType.none) {
    if(widget.currentCacu.cacuValue == '0') {
    widget.currentCacu.cacuValue = num;
    } else {
    widget.currentCacu.cacuValue = widget.currentCacu.cacuValue + num;
    }
    }
    else {
    context.read<CacuDao>().insertCacu(widget.currentCacu);
    widget.currentCacu = Cacu(num, CacuType.none);
    }
    });
    }
    ),
    const SizedBox(width: 5),
    OutlinedButton(
    child: const Text('2'),
    onPressed: (){
    setState(() {
    String num = '2';
    if(widget.currentCacu.cacuType == CacuType.none) {
    if(widget.currentCacu.cacuValue == '0') {
    widget.currentCacu.cacuValue = num;
    } else {
    widget.currentCacu.cacuValue = widget.currentCacu.cacuValue + num;
    }
    }
    else {
    context.read<CacuDao>().insertCacu(widget.currentCacu);
    widget.currentCacu = Cacu(num, CacuType.none);
    }
    });
    }
    ),
    const SizedBox(width: 5),
    OutlinedButton(
    child: const Text('3'),
    onPressed: (){
    setState(() {
    String num = '3';
    if(widget.currentCacu.cacuType == CacuType.none) {
    if(widget.currentCacu.cacuValue == '0') {
    widget.currentCacu.cacuValue = num;
    } else {
    widget.currentCacu.cacuValue = widget.currentCacu.cacuValue + num;
    }
    }
    else {
    context.read<CacuDao>().insertCacu(widget.currentCacu);
    widget.currentCacu = Cacu(num, CacuType.none);
    }
    });
    }
    ),
    const SizedBox(width: 10),
    OutlinedButton(
    child: const Text('+'),
    onPressed: (){
    setState(() {
    widget.currentCacu.cacuType = CacuType.addition;
    });
    }
    ),
    ],
    ),
    const SizedBox(height: 5),
    Row(
    mainAxisAlignment: MainAxisAlignment.center,
    children: [
    SizedBox(
    width: 130.0,
    child: OutlinedButton(
    child: const Text('0'),
    onPressed: (){
    setState(() {
    String num = '0';
    if(widget.currentCacu.cacuType == CacuType.none) {
    if(widget.currentCacu.cacuValue != '0' && !widget.currentCacu.cacuValue.contains('.')) {
    widget.currentCacu.cacuValue = widget.currentCacu.cacuValue + num;
    }
    }
    else {
    context.read<CacuDao>().insertCacu(widget.currentCacu);
    widget.currentCacu = Cacu(num, CacuType.none);
    }
    });
    }
    ),
    ),
    const SizedBox(width: 5),
    OutlinedButton(
    child: const Text('.'),
    onPressed: (){
    String num = '.';
    if(widget.currentCacu.cacuType == CacuType.none) {
    if(!widget.currentCacu.cacuValue.contains('.')) {
    widget.currentCacu.cacuValue = widget.currentCacu.cacuValue + num;
    }
    }
    else {
    context.read<CacuDao>().insertCacu(widget.currentCacu);
    widget.currentCacu = Cacu('0', CacuType.none);
    }
    }
    ),
    const SizedBox(width: 10),
    OutlinedButton(
    child: const Text('='),
    onPressed: (){
    setState(() {
    double result = 0;
    CacuType lastCacuType = CacuType.none;

    List<Cacu> cacus = context.read<CacuDao>().getCacus();
    cacus.add(Cacu(widget.currentCacu.cacuValue, CacuType.none));
    for (var cacu in cacus) {
    switch(lastCacuType) {
    case CacuType.division:
    result = result / double.parse(cacu.cacuValue);
    case CacuType.multiplication:
    result = result * double.parse(cacu.cacuValue);
    case CacuType.subtraction:
    result = result - double.parse(cacu.cacuValue);
    case CacuType.addition:
    result = result + double.parse(cacu.cacuValue);
    default:
    result = double.parse(cacu.cacuValue);
    }
    lastCacuType = cacu.cacuType;
    }

    widget.currentCacu = Cacu(result.toString(), CacuType.none);

    context.read<CacuDao>().clearCacu();
    });
    }
    ),
    ],
    ),
    const SizedBox(height: 5),
    ]);

    return Scaffold(
    appBar: AppBar(title: const Text("Caculator"),),
    body: Container(
    alignment: Alignment.topCenter,
    child: Column(
    children: [
    Expanded(
    child: ListView(
    controller: _scrollController,
    children: [
    ...context.watch<CacuDao>().getCacus().map((e) => CacuComponent(e)).toList(),
    ],
    ),
    ),
    valueViewer,
    keybooards,
    ]
    ),
    ),
    );
    }
    }