Category: Tech

IT Technology, 3C, AI

  • Dart 的物件繼承

    Dart 的物件繼承

    • extends 繼承單一物件
    • implements 繼承且覆寫所有方法 (Dart 沒有提供 interface 的功能,但可透過 implements 達到相同目的)
    • mixins 繼承多個物件(多重繼承)
  • 解決 Flutter 編譯 Windows 程式出錯 Exception: Building with plugins requires symlink support

    在編譯成 Windows 程式時,若偵錯主控台出現如下般錯誤:

    Launching lib\main.dart on Windows in debug mode...
    Exception: Building with plugins requires symlink support.

    Please enable Developer Mode in your system settings. Run
    start ms-settings:developers
    to open settings.

    則可循該錯誤訊息的指示,開啟 Windows 的開發者模式,以便其能順利執行偵錯

    1. [Win]+[R] 開啟執行視窗,輸入
    ms-settings:developers

    2. 啟用[開發人員模式]即可

  • Flutter Web Service Client Sample 範例 (續) – 可編輯的表格內容

    Flutter Web Service Client Sample 範例 (續) – 可編輯的表格內容

    主要差異為 DataCell 內的 Text 改成可編輯的 TextField。

    components/post_table_with_search_and_edit.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';
    import 'package:http/http.dart' as http;

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

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

    class _PostTableWithSearchAndEdit extends State<PostTableWithSearchAndEdit> {
    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(
    TextField(
    controller: TextEditingController(text: postJson[key].toString()),
    onSubmitted: (inputStr){
    postJson[key] = inputStr;

    // Post Data
    var url = Uri.parse('https://jsonplaceholder.typicode.com/posts');
    var res = http.post(url, body: jsonEncode(postJson));
    res.then((value) => print(value.body),);
    },
    ));
    }).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),
    )
    ]),
    ),
    );
    }
    }
  • 關於 Flutter 的 StreamBuilder

    關於 Flutter 的 StreamBuilder

    在 Flutter 中,StreamBuilder 是一個非常有用的 widget,它可以根據與 Stream 的最新交互快照自我構建。這個 widget 特別適合於需要即時更新內容的應用場景,如聊天應用、社交網絡等。

    StreamBuilder 的基本用法如下:

    StreamBuilder(
    stream: stream, // 你的 Stream 對象
    builder: (BuildContext context, AsyncSnapshot snapshot) {
    // 這裡是 UI 構建邏輯,可以根據 snapshot 的數據來決定顯示什麼
    },
    )

    當提供一個 Stream 給 StreamBuilder時,它會監聽 Stream 的事件。每當其發出一個新的數據項目,builder 函數就會被調用,並且 AsyncSnapshot 會包含新的數據信息。這樣便可根據數據的變化來更新 UI。

    如果 Stream 發出一個錯誤,AsyncSnapshot 會帶有錯誤信息,我們可以決定如何處理這個錯誤(比如顯示一個錯誤提示)。當 Stream 完成所有事件的發送,AsyncSnapshot 的連接狀態會變為 ConnectionState.done,這時也可以進行相應的處理。

    另外,Stream和另一個也用來處理非同步事件的 Future 比,其差異如下:

    1. 數據傳遞
      • Future 代表一個將來某時可能會變得可用的單一結果或錯誤。當結果可用時,接收者可以註冊回調來處理該值或錯誤。
      • Stream 提供一個事件的連續流,這些事件隨著時間變化而變化,我們可以對這些變化作出反應。
    2. 訂閱
      • Future 只能被消費一次,當它完成時,便不能再從它那裡獲取更多的數據。
      • Stream 可以有多個訂閱者,這意味著多個組件可以從同一個流接收更新。

    如果需要一個一次性的非同步結果,使用 Future 及對應的 FutureBuilder ,如果需要監聽一個隨時間變化的數據序列,則就用Stream 及對應的 StreamBuilder。

  • 關於 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'],
    );
    }
    }