You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter技术问询:如何用fetchTitle从JSON填充列表

How to Populate GridView with Async JSON Data in Flutter

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:


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: The build method runs frequently, so calling fetchTitle() there would trigger repeated network requests.
  • State management: Both approaches handle state changes properly. FutureBuilder does it automatically, while the StatefulWidget approach uses setState to update the UI when data is ready.
  • Uri.parse: Newer versions of the http package require a Uri instead of a raw String for the URL, so I updated that in the fetch method.

内容的提问来源于stack exchange,提问作者jancooth

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.07 10:07:36