Flutter技术问询:如何用fetchTitle从JSON填充列表
Hey there! Let's tackle this problem step by step. Since we're dealing with asynchronous data fetching in Flutter, we need to make sure we handle the data lifecycle properly so our GridView updates once the data is ready. Here are two solid approaches to achieve this:
Approach 1: Use FutureBuilder (Recommended)
FutureBuilder is Flutter's built-in widget designed to handle asynchronous operations and update the UI automatically as the future resolves. It's clean and follows Flutter's best practices for async UI.
First, make sure your Titles model class and parsing logic are set up (I'll include a typical example since you mentioned titlesFromJson):
import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; // Your Titles model class class Titles { final String title; Titles({required this.title}); factory Titles.fromJson(Map<String, dynamic> json) { return Titles(title: json['title']); } } // Parsing helper method List<Titles> titlesFromJson(String str) => List<Titles>.from(json.decode(str).map((x) => Titles.fromJson(x)));
Now, integrate FutureBuilder with your GridView:
class TitleGridView extends StatelessWidget { // Your existing fetch method (updated to use Uri.parse for newer http package versions) Future<List<Titles>> fetchTitle() async { String url = "https://jsonplaceholder.typicode.com/posts"; final response = await http.get( Uri.parse(url), headers: {'Content-Type': 'application/json'} ); return titlesFromJson(utf8.decode(response.bodyBytes)); } @override Widget build(BuildContext context) { return Scaffold( body: FutureBuilder<List<Titles>>( future: fetchTitle(), builder: (context, snapshot) { // Handle different async states if (snapshot.connectionState == ConnectionState.waiting) { // Show loading indicator while data is fetching return const Center(child: CircularProgressIndicator()); } else if (snapshot.hasError) { // Show error message if something goes wrong return Center(child: Text('Oops! Error: ${snapshot.error}')); } else if (!snapshot.hasData || snapshot.data!.isEmpty) { // Show empty state if no data is returned return const Center(child: Text('No titles available')); } else { // Data is ready! Build the GridView final titleList = snapshot.data!; return Expanded( child: GridView.count( crossAxisCount: 2, crossAxisSpacing: 10, mainAxisSpacing: 10, childAspectRatio: 1, padding: const EdgeInsets.only(left: 20, top: 20, right: 20), children: List.generate(titleList.length, (index) { return InkWell( onTap: () { // Add your onTap logic here }, child: Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( border: Border.all(color: Colors.grey.shade300), borderRadius: BorderRadius.circular(8), ), child: Text( titleList[index].title, style: const TextStyle(fontSize: 14), overflow: TextOverflow.ellipsis, maxLines: 3, ), ), ); }), ) ); } }, ), ); } }
Approach 2: Use StatefulWidget with initState
If you prefer more control over the data loading state (like adding a refresh button later), you can use a StatefulWidget and fetch data in initState:
import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; class Titles { final String title; Titles({required this.title}); factory Titles.fromJson(Map<String, dynamic> json) => Titles(title: json['title']); } List<Titles> titlesFromJson(String str) => List<Titles>.from(json.decode(str).map((x) => Titles.fromJson(x))); class TitleGridView extends StatefulWidget { const TitleGridView({super.key}); @override State<TitleGridView> createState() => _TitleGridViewState(); } class _TitleGridViewState extends State<TitleGridView> { List<Titles> myLists = []; bool isLoading = true; String? errorMsg; Future<void> loadTitles() async { try { final fetchedTitles = await fetchTitle(); setState(() { myLists = fetchedTitles; isLoading = false; }); } catch (e) { setState(() { errorMsg = e.toString(); isLoading = false; }); } } Future<List<Titles>> fetchTitle() async { String url = "https://jsonplaceholder.typicode.com/posts"; final response = await http.get( Uri.parse(url), headers: {'Content-Type': 'application/json'} ); return titlesFromJson(utf8.decode(response.bodyBytes)); } @override void initState() { super.initState(); // Start fetching data as soon as the widget initializes loadTitles(); } @override Widget build(BuildContext context) { return Scaffold( body: isLoading ? const Center(child: CircularProgressIndicator()) : errorMsg != null ? Center(child: Text('Error: $errorMsg')) : myLists.isEmpty ? const Center(child: Text('No titles found')) : Expanded( child: GridView.count( crossAxisCount: 2, crossAxisSpacing: 10, mainAxisSpacing: 10, childAspectRatio: 1, padding: const EdgeInsets.only(left: 20, top: 20, right: 20), children: List.generate(myLists.length, (index) { return InkWell( onTap: () { // Add tap functionality here }, child: Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( border: Border.all(color: Colors.grey.shade300), borderRadius: BorderRadius.circular(8), ), child: Text( myLists[index].title, style: const TextStyle(fontSize: 14), overflow: TextOverflow.ellipsis, maxLines: 3, ), ), ); }), ) ), ); } }
Key Notes:
- Avoid calling async methods directly in
build: Thebuildmethod runs frequently, so callingfetchTitle()there would trigger repeated network requests. - State management: Both approaches handle state changes properly.
FutureBuilderdoes it automatically, while the StatefulWidget approach usessetStateto update the UI when data is ready. - Uri.parse: Newer versions of the
httppackage require aUriinstead of a raw String for the URL, so I updated that in the fetch method.
内容的提问来源于stack exchange,提问作者jancooth

