แนวทางจัดการ Layout แสดงรายการสินค้าแบบต่างๆใน Flutter

บทความใหม่ ยังไม่ถึงปี โดย Ninenik Narkdee
listview masonrygridview gridview layout product

คำสั่ง การ กำหนด รูปแบบ ตัวอย่าง เทคนิค ลูกเล่น การประยุกต์ การใช้งาน เกี่ยวกับ listview masonrygridview gridview layout product

ดูแล้ว 573 ครั้ง


เนื้อหาตอนต่อไปนี้ จะนำตัาอย่างแนวทางการจัดรูปแบบการแสดง
รายการสินค้าในแบบต่างๆ ให้สามารถนำไปใช้งานหรือประยุกต์ต่อได้
จะเป็นเนื้อหาที่ต่อยอดจากบทความ ตามลิ้งค์ด้านล่าง
 
การ Cache ข้อมูลเพิ่มความเร็วสำหรับการโหลดข้อมูล Server http://niik.in/1106
 
เนื้อหานี้จะโค้ดตัวอย่างในตอนท้ายบทความสามารถดาวน์โหลดไปดูเป็นทางได้
โดยในโค้ดตัวอย่างจะมีการใช้งาน package ต่างๆ ที่เกี่ยวข้อง รวมถึงตัวอย่างการนำ Riverpod
มาใช้งานร่วมกับ Provider ในโปรเจ็คเดียวกัน ซึ่งเป็นตัวอย่างกรณีที่เราอาจจะต้องใช้งานร่วมกัน
แต่จริงๆ แล้วควรเลือกอย่างใดอย่างหนึ่ง
 

ในการจัดรูปแบบ layout ตัวอย่างนี้ จะมีด้วยกัน 4 รูปแบบ ดังนี้คือ

    - แบบ ListView ใช้ ListTile จัดรูปแบบ
    - แบบ ListView ไม่ใช้ ListTile จัดรูปแบบ แต่กำหนดเอง
    - แบบ GridView 
    - ใช้ MasonryGridView ที่สามารถกำหนดให้ความสูงแต่ละ Grid แตกต่างกันได้
 

ตัวอย่างผลลัพธ์แต่ละแบบ

 
 
    ในแบบที่สี่หรือแบบสุดท้าย MasonryGridView เรามีการใช้งาน package ที่ชื่อว่า
flutter_staggered_grid_view เข้ามาช่วย ติดตั้งก่อนใช้งานในไฟล์ pubspec.yaml
 
flutter_staggered_grid_view: ^0.7.0
 
    ในตัวอย่างแต่ละหัวข้อ จะนำเฉพาะส่วนของโค้ดที่กำหนดรูปแบบเท่านั้น มาให้ดูเป็นตัวอย่าง
โดยโค้ดสุดท้าย จะเป็นไฟล์รวม product.dart ที่รวมทั้งหมด และมีการคอมเม้นท์ปิดแต่ละรูปแบบ
ไว้และเปิดไว้อันเดียว  ไฟล์ท้้งหมดมีในโค้ดท้ายบทความให้ด้วยโหลด
 

การจัด Layout ด้วย ListView ใช้ ListTile จัดรูปแบบ

    รูปแบบการแสดงจะเป็นในรูปแบบ ListTile ที่เราคุ้นเคย สามารถปรับแต่งเพิ่มเติมได้ตามต้องการ
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
// ใช้งาน ListView
child: ListView.separated(
  // กรณีมีรายการ แสดงปกติ
  controller:
      _scrollController, // กำนหนด controller ที่จะใช้งานร่วม
  itemCount: items.length,
  itemBuilder: (context, index) {
    Product product = items[index];
 
    Widget card; // สร้างเป็นตัวแปร
    card = Card(
        margin: const EdgeInsets.all(
            5.0), // การเยื้องขอบ
        child: Column(
          children: [
            ListTile(
              leading: CachedNetworkImage(
                imageUrl: product.image,
                width: 100.0,
                placeholder: (context, url) =>
                    Center(
                  child: SizedBox(
                    // Adjust the size as needed
                    width: 40.0,
                    height: 40.0,
                    child:
                        CircularProgressIndicator(), // Show loading indicator
                  ),
                ),
                errorWidget: (context, url,
                        error) =>
                    Icon(Icons
                        .error), // Show error icon if loading fails
              ),
              title: Text(product.title),
              subtitle: Text(
                  'Price: \$ ${product.price}'),
              trailing: Icon(Icons.more_vert),
              onTap: () {},
            )
          ],
        ));
    return card;
  },
  separatorBuilder:
      (BuildContext context, int index) =>
          const SizedBox(),
),
// ใช้งาน ListView
 
 

การจัด Layout ด้วย ListView ไม่ใช้ ListTile จัดรูปแบบ

    เราสามารถจัดรูปแบบตามต้องการแทนการใช้งาน ListTile ได้ทำให้มีความหลากหลายมากขึ้น
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
  // ใช้งาน ListView แบบกำหนดเอง ไม่ใช้ ListTile
 child: ListView.separated(
  // กรณีมีรายการ แสดงปกติ
  controller:
      _scrollController, // กำนหนด controller ที่จะใช้งานร่วม
  itemCount: items.length,
  itemBuilder: (context, index) {
    Product product = items[index];
 
    Widget card; // สร้างเป็นตัวแปร
    card = Card(
        margin: const EdgeInsets.all(5.0),
        child: Row(
          mainAxisAlignment:  MainAxisAlignment.start,
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Padding(
              padding: const EdgeInsets.all(8.0),
              child: CachedNetworkImage(
                  imageUrl: product.image,
                  height: 100.0,
                  width: 100.0,
                  fit: BoxFit.contain,
                  placeholder: (context, url) =>
                      Center(
                    child: SizedBox(
                      // Adjust the size as needed
                      width: 40.0,
                      height: 40.0,
                      child: CircularProgressIndicator(),
                    ),
                  ),
                  errorWidget: (context, url,error) =>Icon(Icons.error),
                ),
            ),
              Expanded(
                child: Padding(
                  padding: const EdgeInsets.all(0.0),
                  child: Container(
                    color: Colors.grey[200],
                    child: Column(
                      mainAxisAlignment:  MainAxisAlignment.start,
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: [
                        Text(product.title),
                        Text('Price: \$ ${product.price}'),
                      ],
                    ),
                  ),
                ),
              ),   
          ],
        ));
    return card;
  },
  separatorBuilder:(BuildContext context, int index) =>const SizedBox(),
),
// ใช้งาน ListView แบบกำหนดเอง ไม่ใช้ ListTile
 
 

การจัด Layout ด้วย GridView 

    จัดรูปแบบในลักษณะ Grid ที่มีความสูงของรายการข้อมูลเท่าๆ กัน
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
// ใช้งาน GridView
 child: GridView.builder(
controller: _scrollController,
padding: const EdgeInsets.all(5.0),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
  crossAxisCount: 2, // Number of columns
  crossAxisSpacing: 0.0,
  mainAxisSpacing: 0.0,
  childAspectRatio: 3 / 3.8, // Adjust to control the size ratio
),
itemCount: items.length,
itemBuilder: (context, index) {
 
  Product product = items[index];
 
    Widget card; // สร้างเป็นตัวแปร
    card = Card(
        child: Padding(
          padding: const EdgeInsets.all(3.0),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              Align(
                child: CachedNetworkImage(
                    imageUrl: product.image,
                    height: 150,
                    fit: BoxFit.contain,
                    placeholder: (context, url) =>
                        const Center(
                      child: SizedBox(
                        width: 40.0,
                        height: 40.0,
                        child:CircularProgressIndicator(),
                      ),
                    ),
                    errorWidget: (context, url,
                            error) =>
                        const Icon(Icons.error),
                  ),
              ),
                Padding(
                  padding: const EdgeInsets.all(3.0),
                  child: Text(product.title,
                  maxLines: 2,
                  overflow: TextOverflow.ellipsis,),
                ),
                Padding(
                  padding: const EdgeInsets.all(3.0),
                  child: Text('Price: \$ ${product.price}'),
                ),
            ],
          ),
        ));
        return card;
    },
  ),
  // ใช้งาน GridView
 

การจัด Layout ด้วย MasonryGridView

    จัดรูปแบบในลักษณะ Grid ที่มีความสูงของรายการข้อมูลเป็นไปตามเนื้อหาภายในของแต่ละรายการ
ทำให้ให้รายการดูมีลักษณะเด่นพิเศษตามชนิดข้อมูลของรายการนั้นๆ เช่น ถ้ารูปรายการนั้นใหญ่ก็อาจจะ
แสดงเด่นกว่ารายการอื่น 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
// ใช้งาน MasonryGridView
child: MasonryGridView.builder(
  controller: _scrollController,
  padding: const EdgeInsets.all(5.0),
  gridDelegate: SliverSimpleGridDelegateWithFixedCrossAxisCount(
    crossAxisCount: 2, // Number of columns
  ),
  itemCount: items.length,
  itemBuilder: (context, index) {
    Product product = items[index];
 
    return Card(
      child: Padding(
        padding: const EdgeInsets.all(3.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            CachedNetworkImage(
              imageUrl: product.image,
              fit: BoxFit.contain,
              placeholder: (context, url) => const Center(
                child: SizedBox(
                  width: 40.0,
                  height: 40.0,
                  child: CircularProgressIndicator(),
                ),
              ),
              errorWidget: (context, url, error) => const Icon(Icons.error),
            ),
            Padding(
              padding: const EdgeInsets.all(3.0),
              child: Text(
                product.title,
                maxLines: 2,
                overflow: TextOverflow.ellipsis,
              ),
            ),
            Padding(
              padding: const EdgeInsets.all(3.0),
              child: Text('Price: \$ ${product.price}'),
            ),
          ],
        ),
      ),
    );
  },
),
// ใช้งาน MasonryGridView
 
    เพื่อให้เห็นภาพรวมของตัวอย่างโค้ด ให้ดูไฟล์ทั้งหมด ได้ดังนี้ ส่วนของโค้ด มีการจัดการต่างๆ เกี่ยวกับ
รายการสินค้า ในเนื้อหาที่ผ่านมา
 

ไฟล์ product.dart

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
import 'dart:async';
import 'dart:convert';
import 'dart:io';
  
import 'package:flutter/material.dart';
import 'package:flutter/foundation.dart';
import 'package:http/http.dart' as http;
// import 'package:intl/intl.dart'; // จัดรูปแบบวันทีและเวลา http://niik.in/1047
import 'package:cached_network_image/cached_network_image.dart';
import 'package:path_provider/path_provider.dart';
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
  
import '../models/product_model.dart';
   
class Products extends StatefulWidget {
    static const routeName = '/product';
  
    const Products({Key? key}) : super(key: key);
   
    @override
    State<StatefulWidget> createState() {
        return _ProductsState();
    }
}
   
class _ProductsState extends State<Products> {
  // สร้างตัวแปรที่สามารถแจ้งเตือนการเปลี่ยนแปลงค่า
  final ValueNotifier<bool> _visible = ValueNotifier<bool>(false);
  // กำนหดตัวแปรข้อมูล products
  Future<List<Product>> _products = Future.value([]);
  // ตัว ScrollController สำหรับจัดการการ scroll ใน ListView
  final ScrollController _scrollController = ScrollController();
  // สำหรับป้องกันการเรียกโหลดข้อมูลซ้ำในทันที
  bool _isLoading = false;
  
  // จำลองใช้เป็นแบบฟังก์ชั่น ให้เสมือนดึงข้อมูลจาก server
  Future<String> fetchData() async {
    print("debug: do function");
    final response = await Future<String>.delayed(
      const Duration(seconds: 2),
      () {
        return 'Data Loaded \n${DateTime.now()}';
      },
    );
    return response;
  }
  
  Future<void> _refresh() async {
    if (_isLoading) return;
  
    _visible.value = true;
    try {
      setState(() {
        _isLoading = true;
        _products = fetchProduct(reload: true);
      });
    } catch (e) {
      throw Exception('error: ${e}');
    } finally {
      setState(() {
        _isLoading = false;
      });
    }
  }
  
  @override
  void initState() {
    print("debug: Init");
    super.initState();
    _products = fetchProduct();
  }
  
  @override
  void dispose() {
    _scrollController.dispose();
    _visible.dispose(); // Dispose the ValueNotifier
    super.dispose();
  }
  
  @override
  Widget build(BuildContext context) {
    print("debug: build");
    return Scaffold(
      appBar: AppBar(
        title: Text('Product'),
        actions: [
          IconButton(
            onPressed: () async {
              if (!_isLoading && _visible.value == false) {
                _refresh();
              }
            },
            icon: const Icon(Icons.refresh_outlined),
          )
        ],
      ),
      body: ListView(
        padding: const EdgeInsets.all(0.0),
        children: [
          ValueListenableBuilder<bool>(
            valueListenable: _visible,
            builder: (context, visible, child) {
              return Visibility(
                visible: visible,
                child: const LinearProgressIndicator(
                  backgroundColor: Colors.white60,
                ),
              );
            },
          ),
          FutureBuilder<List<Product>>(
            // ชนิดของข้อมูล
            future: _products,
            builder: (context, snapshot) {
              if (snapshot.connectionState == ConnectionState.waiting) {}
              if (snapshot.connectionState == ConnectionState.done) {
                WidgetsBinding.instance.addPostFrameCallback((_) {
                  // Change state after the build is complete
                  _visible.value = false;
  
                  if (_scrollController.hasClients) {
                    //เช็คว่ามีตัว widget ที่ scroll ได้หรือไม่ ถ้ามี
                    // เลื่อน scroll มาด้านบนสุด
                    _scrollController.animateTo(0,
                        duration: Duration(milliseconds: 500),
                        curve: Curves.fastOutSlowIn);
                  }
                });
              }
              if (snapshot.hasData) {
                // แสดงทั้งหมด
                final items = snapshot.data!.toList();
                // แสดงแค่ 10 รายการ
                // final items = snapshot.data!.take(10).toList();
                double statusBarHeight = MediaQuery.of(context).padding.top;
                double appBarHeight = kToolbarHeight; // Default height of the AppBar (56.0)
                double availableHeight = MediaQuery.of(context).size.height - statusBarHeight - appBarHeight - 80;
                print("debug: ${statusBarHeight+kToolbarHeight+80}");
                  
                return Column(
                  children: [
                    Container(
                      // สร้างส่วน header ของลิสรายการ
                      padding: const EdgeInsets.all(5.0),
                      decoration: BoxDecoration(
                        color: Colors.orange.withAlpha(100),
                      ),
                      child: Row(
                        children: [
                          Text(
                              'Total ${items.length} items'), // แสดงจำนวนรายการ
                        ],
                      ),
                    ),
                    SizedBox(
                      // ปรับความสูงขางรายการทั้งหมด  การ ลบค่า เพื่อให้ข้อมูลแสดงเต็มพื้นที่
                      // หากมี appbar ควรลบ 100 ถ้ามีส่วนอื่นเพิ่มให้บวกเพิ่มเข้าไป ตามเหมาะสม
                      // หากไม่มี appbar ควรลบพื้นที่ที่เพิ่มเข้ามาค่าอื่นๆ ตามเหมาะสม
                      height: MediaQuery.of(context).size.height - 136,
                      child: snapshot.data!.isNotEmpty // กำหนดเงื่อนไขตรงนี้
                          ? RefreshIndicator(
                              onRefresh: () async {
                                if (!_isLoading && _visible.value == false) {
                                  _refresh();
                                }
                              },
                              // ใช้งาน MasonryGridView
                              child: MasonryGridView.builder(
                                controller: _scrollController,
                                padding: const EdgeInsets.all(5.0),
                                gridDelegate: SliverSimpleGridDelegateWithFixedCrossAxisCount(
                                  crossAxisCount: 2, // Number of columns
                                ),
                                itemCount: items.length,
                                itemBuilder: (context, index) {
                                  Product product = items[index];
 
                                  return Card(
                                    child: Padding(
                                      padding: const EdgeInsets.all(3.0),
                                      child: Column(
                                        crossAxisAlignment: CrossAxisAlignment.start,
                                        children: [
                                          CachedNetworkImage(
                                            imageUrl: product.image,
                                            fit: BoxFit.contain,
                                            placeholder: (context, url) => const Center(
                                              child: SizedBox(
                                                width: 40.0,
                                                height: 40.0,
                                                child: CircularProgressIndicator(),
                                              ),
                                            ),
                                            errorWidget: (context, url, error) => const Icon(Icons.error),
                                          ),
                                          Padding(
                                            padding: const EdgeInsets.all(3.0),
                                            child: Text(
                                              product.title,
                                              maxLines: 2,
                                              overflow: TextOverflow.ellipsis,
                                            ),
                                          ),
                                          Padding(
                                            padding: const EdgeInsets.all(3.0),
                                            child: Text('Price: \$ ${product.price}'),
                                          ),
                                        ],
                                      ),
                                    ),
                                  );
                                },
                              ),
                              // ใช้งาน MasonryGridView
 
                              // ใช้งาน GridView
/*                               child: GridView.builder(
                              controller: _scrollController,
                              padding: const EdgeInsets.all(5.0),
                              gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
                                crossAxisCount: 2, // Number of columns
                                crossAxisSpacing: 0.0,
                                mainAxisSpacing: 0.0,
                                childAspectRatio: 3 / 3.8, // Adjust to control the size ratio
                              ),
                              itemCount: items.length,
                              itemBuilder: (context, index) {
 
                                Product product = items[index];
  
                                  Widget card; // สร้างเป็นตัวแปร
                                  card = Card(
                                      child: Padding(
                                        padding: const EdgeInsets.all(3.0),
                                        child: Column(
                                          crossAxisAlignment: CrossAxisAlignment.start,
                                          children: [
                                            Align(
                                              child: CachedNetworkImage(
                                                  imageUrl: product.image,
                                                  height: 150,
                                                  fit: BoxFit.contain,
                                                  placeholder: (context, url) =>
                                                      const Center(
                                                    child: SizedBox(
                                                      width: 40.0,
                                                      height: 40.0,
                                                      child:CircularProgressIndicator(),
                                                    ),
                                                  ),
                                                  errorWidget: (context, url,
                                                          error) =>
                                                      const Icon(Icons.error),
                                                ),
                                            ),
                                              Padding(
                                                padding: const EdgeInsets.all(3.0),
                                                child: Text(product.title,
                                                maxLines: 2,
                                                overflow: TextOverflow.ellipsis,),
                                              ),
                                              Padding(
                                                padding: const EdgeInsets.all(3.0),
                                                child: Text('Price: \$ ${product.price}'),
                                              ),
                                          ],
                                        ),
                                      ));
                                      return card;
                                  },
                                ), */
                                // ใช้งาน GridView
 
                                // ใช้งาน ListView แบบกำหนดเอง ไม่ใช้ ListTile
/*                               child: ListView.separated(
                                // กรณีมีรายการ แสดงปกติ
                                controller:
                                    _scrollController, // กำนหนด controller ที่จะใช้งานร่วม
                                itemCount: items.length,
                                itemBuilder: (context, index) {
                                  Product product = items[index];
  
                                  Widget card; // สร้างเป็นตัวแปร
                                  card = Card(
                                      margin: const EdgeInsets.all(5.0),
                                      child: Row(
                                        mainAxisAlignment:  MainAxisAlignment.start,
                                        crossAxisAlignment: CrossAxisAlignment.start,
                                        children: [
                                          Padding(
                                            padding: const EdgeInsets.all(8.0),
                                            child: CachedNetworkImage(
                                                imageUrl: product.image,
                                                height: 100.0,
                                                width: 100.0,
                                                fit: BoxFit.contain,
                                                placeholder: (context, url) =>
                                                    Center(
                                                  child: SizedBox(
                                                    // Adjust the size as needed
                                                    width: 40.0,
                                                    height: 40.0,
                                                    child: CircularProgressIndicator(),
                                                  ),
                                                ),
                                                errorWidget: (context, url,error) =>Icon(Icons.error),
                                              ),
                                          ),
                                            Expanded(
                                              child: Padding(
                                                padding: const EdgeInsets.all(0.0),
                                                child: Container(
                                                  color: Colors.grey[200],
                                                  child: Column(
                                                    mainAxisAlignment:  MainAxisAlignment.start,
                                                    crossAxisAlignment: CrossAxisAlignment.start,
                                                    children: [
                                                      Text(product.title),
                                                      Text('Price: \$ ${product.price}'),
                                                    ],
                                                  ),
                                                ),
                                              ),
                                            ),   
                                        ],
                                      ));
                                  return card;
                                },
                                separatorBuilder:(BuildContext context, int index) =>const SizedBox(),
                              ), */
                              // ใช้งาน ListView แบบกำหนดเอง ไม่ใช้ ListTile                             
 
                                // ใช้งาน ListView
 /*                              child: ListView.separated(
                                // กรณีมีรายการ แสดงปกติ
                                controller:
                                    _scrollController, // กำนหนด controller ที่จะใช้งานร่วม
                                itemCount: items.length,
                                itemBuilder: (context, index) {
                                  Product product = items[index];
  
                                  Widget card; // สร้างเป็นตัวแปร
                                  card = Card(
                                      margin: const EdgeInsets.all(
                                          5.0), // การเยื้องขอบ
                                      child: Column(
                                        children: [
                                          ListTile(
                                            leading: CachedNetworkImage(
                                              imageUrl: product.image,
                                              width: 100.0,
                                              placeholder: (context, url) =>
                                                  Center(
                                                child: SizedBox(
                                                  // Adjust the size as needed
                                                  width: 40.0,
                                                  height: 40.0,
                                                  child:
                                                      CircularProgressIndicator(), // Show loading indicator
                                                ),
                                              ),
                                              errorWidget: (context, url,
                                                      error) =>
                                                  Icon(Icons
                                                      .error), // Show error icon if loading fails
                                            ),
                                            title: Text(product.title),
                                            subtitle: Text(
                                                'Price: \$ ${product.price}'),
                                            trailing: Icon(Icons.more_vert),
                                            onTap: () {},
                                          )
                                        ],
                                      ));
                                  return card;
                                },
                                separatorBuilder:
                                    (BuildContext context, int index) =>
                                        const SizedBox(),
                              ), */
                              // ใช้งาน ListView
 
 
                            )
                          : const Center(
                              child: Text('No items')), // กรณีไม่มีรายการ
                    ),
                  ],
                );
              } else if (snapshot.hasError) {
                return Center(child: Text('${snapshot.error}'));
              }
              return const Center(child: CircularProgressIndicator());
            },
          ),
        ],
      ),
      floatingActionButton: ValueListenableBuilder<bool>(
        valueListenable: _visible,
        builder: (context, visible, child) {
          return (visible == false)
              ? FloatingActionButton(
                  onPressed: () async {
                    if (!_isLoading && _visible.value == false) {
                      _refresh();
                    }
                  },
                  shape: const CircleBorder(),
                  child: const Icon(Icons.refresh),
                )
              : SizedBox.shrink();
        },
      ),
    );
  }
}
  
// สรัางฟังก์ชั่นดึงข้อมูล คืนค่ากลับมาเป็นข้อมูล Future ประเภท List ของ Product
Future<List<Product>> fetchProduct({reload}) async {
  String _currentPath = ''; // เก็บ path ปัจจุบัน
  final appDocumentsDirectory = await getApplicationDocumentsDirectory();
  
  _currentPath = appDocumentsDirectory.path;
  
  String filename = "product_cache.json";
  String readFile = "$_currentPath/$filename";
  
  String _jsonData = '';
  final _file = File(readFile);
  final isExits = await _file.exists();
  
  try {
    if (isExits && reload == null) {
      print("debug: read from file");
      _jsonData = await _file.readAsString();
      return compute(parseProducts, _jsonData);
    } else {
      // ทำการดึงข้อมูลจาก server ตาม url ที่กำหนด
      String url = 'https://fakestoreapi.com/products';
      final response = await http.get(Uri.parse(url));
  
      // เมื่อมีข้อมูลกลับมา
      if (response.statusCode == 200) {
        print("debug: load form server");
        final myfile = _file;
        final isExits = await myfile.exists(); // เช็คว่ามีไฟล์หรือไม่
        if (!isExits) {
          // ถ้ายังไม่มีไฟล์
          try {
            await myfile.writeAsString(response.body);
          } catch (e) {
            throw Exception('error: ${e}');
          }
        } else {
          try {
            await myfile.writeAsString(response.body);
          } catch (e) {
            throw Exception('error: ${e}');
          }
        }
        // ส่งข้อมูลที่เป็น JSON String data ไปทำการแปลง เป็นข้อมูล List<Product
        // โดยใช้คำสั่ง compute ทำงานเบื้องหลัง เรียกใช้ฟังก์ชั่นชื่อ parseProducts
        // ส่งข้อมูล JSON String data ผ่านตัวแปร response.body
        return compute(parseProducts, response.body);
      } else {
        // กรณี error
        throw Exception('Failed to load product');
      }
    }
  } catch (e) {
    throw Exception('error: ${e}');
  }
}
  
// ฟังก์ชั่นแปลงข้อมูล JSON String data เป็น เป็นข้อมูล List<Product>
List<Product> parseProducts(String responseBody) {
  final parsed = jsonDecode(responseBody).cast<Map<String, dynamic>>();
  return parsed.map<Product>((json) => Product.fromJson(json)).toList();
}
 
 
    ตัวอย่างและแนวทางโค้ดทั้งหมดนี้ สามารถนำไปประยุกต์ใช้งานได้ทันที และปรับแต่งได้ตามต้องการ
ในโค้ดจะมีแนวทางความรู้ต่างๆ ผสมปะปนอยู่ หวังว่าเนื้อหานี้จะสามารถนำไปต่อยอดไม่มากก็น้อย


   เพิ่มเติมเนื้อหา ครั้งที่ 1 วันที่ 02-09-2024


ดาวน์โหลดโค้ดตัวอย่าง สามารถนำไปประยุกต์ หรือ run ทดสอบได้

http://niik.in/download/flutter/demo_039_02092024_source.rar


กด Like หรือ Share เป็นกำลังใจ ให้มีบทความใหม่ๆ เรื่อยๆ น่ะครับ



อ่านต่อที่บทความ



ทบทวนบทความที่แล้ว









เนื้อหาที่เกี่ยวข้อง






เนื้อหาพิเศษ เฉพาะสำหรับสมาชิก

กรุณาล็อกอิน เพื่ออ่านเนื้อหาบทความ

ยังไม่เป็นสมาชิก

สมาชิกล็อกอิน



( หรือ เข้าใช้งานผ่าน Social Login )




URL สำหรับอ้างอิง











เว็บไซต์ของเราให้บริการเนื้อหาบทความสำหรับนักพัฒนา โดยพึ่งพารายได้เล็กน้อยจากการแสดงโฆษณา โปรดสนับสนุนเว็บไซต์ของเราด้วยการปิดการใช้งานตัวปิดกั้นโฆษณา (Disable Ads Blocker) ขอบคุณครับ