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

Flutter Redux代码异常:无法展示本地Live Server的JSON数据

问题:Flutter Redux无法从本地Live Server加载JSON数据并展示

我跟着YouTube上的Flutter Redux教程学习,教程代码能正常运行,但自己实现后遇到逻辑问题——无法从本地Live Server的JSON文件获取数据并在App中展示。以下是我的代码及对应的people.json文件,请求帮忙排查问题。

我的代码

import 'dart:convert';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:redux/redux.dart';
import 'package:flutter_redux/flutter_redux.dart';

const apiUrl = 'http://127.0.0.1:5500/api/people.json';
void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const HomePage(),
    );
  }
}

@immutable
class Person {
  final String name;
  final int age;
  const Person({required this.name, required this.age});
  Person.fromJson(Map<String, dynamic> json)
      : name = json['name'] as String,
        age = json['age'] as int;

  @override
  String toString() => 'Person ($name, $age years old )';
}

Future<Iterable<Person>> getPersons() async => HttpClient()
    .getUrl(Uri.parse(apiUrl))
    .then((req) => req.close())
    .then((resp) => resp.transform(utf8.decoder).join())
    .then((str) => json.decode(str) as List<dynamic>)
    .then((list) => list.map((e) => Person.fromJson(e)));

@immutable
abstract class Action {
  const Action();
}

@immutable
class LoadPeopleAction extends Action {
  const LoadPeopleAction();
}

@immutable
class SuccessfullyFetchedPeopleAction extends Action {
  final Iterable<Person> persons;
  const SuccessfullyFetchedPeopleAction({required this.persons});
}

@immutable
class FailedToFetchPeopleAction extends Action {
  final Object error;
  const FailedToFetchPeopleAction({required this.error});
}

@immutable
class State {
  final bool isLoading;
  final Iterable<Person>? fetchedPersons;
  final Object? error;

  const State(
      {required this.isLoading,
      required this.fetchedPersons,
      required this.error});

  const State.empty()
      : isLoading = false,
        fetchedPersons = null,
        error = null;
}

State reducer(State oldState, action) {
  if (action is LoadPeopleAction) {
    return const State(isLoading: true, fetchedPersons: null, error: null);
  } else if (action is SuccessfullyFetchedPeopleAction) {
    return State(isLoading: false, fetchedPersons: action.persons, error: null);
  } else if (action is FailedToFetchPeopleAction) {
    return State(
        isLoading: false,
        fetchedPersons: oldState.fetchedPersons,
        error: action.error);
  }
  return oldState;
}

void loadPeopleMiddleware(
    Store<State> store, dynamic action, NextDispatcher next) {
  if (action is LoadPeopleAction) {
    getPersons()
        .then((persons) =>
            store.dispatch(SuccessfullyFetchedPeopleAction(persons: persons)))
        .catchError((e) => store.dispatch(FailedToFetchPeopleAction(error: e)));
  }
  next(action);
}

class HomePage extends StatelessWidget {
  const HomePage({Key? key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    final store = Store(reducer,
        initialState: const State.empty(), middleware: [loadPeopleMiddleware]);
    return Scaffold(
      appBar: AppBar(
        title: const Center(
          child: Text('Flutter Redux Demo'),
        ),
      ),
      body: StoreProvider(
        store: store,
        child: Column(children: [
          TextButton(
              onPressed: () {
                store.dispatch(const LoadPeopleAction());
              },
              child: const Center(child: Text('Load Persons'))),
          StoreConnector<State, bool>(
              converter: (store) => store.state.isLoading,
              builder: (context, isLoading) {
                if (isLoading) {
                  return const CircularProgressIndicator();
                } else {
                  return const SizedBox();
                }
              }),
          StoreConnector<State, Iterable<Person>?>(
              converter: (store) => store.state.fetchedPersons,
              builder: (context, people) {
                if (people == null) {
                  return const SizedBox();
                } else {
                  return Expanded(
                    child: ListView.builder(
                        itemCount: people.length,
                        itemBuilder: (context, index) {
                          final person = people.elementAt(index);
                          return ListTile(
                            title: Text(person.name),
                            subtitle: Text('${person.age} years old.'),
                          );
                        }),
                  );
                }
              })
        ]),
      ),
    );
  }
}

people.json文件

[{
        "name": "Foo",
        "age": 20
    },
    {
        "name": "Bar",
        "age": 30
    }, {
        "name": "F",
        "age": 20
    }
]

问题排查与解决方案

1. Store实例重复创建(核心问题)

你在HomePage的build方法中创建Store,每次页面重建(比如旋转屏幕、状态变化)都会生成新的Store实例,导致之前的请求状态丢失。

修复方式:将Store实例提升到应用顶层,确保全局唯一:

修改main和MyApp:

void main() {
  // 在main中创建Store,确保全局唯一
  final store = Store(reducer,
      initialState: const State.empty(), middleware: [loadPeopleMiddleware]);
  runApp(MyApp(store: store));
}

class MyApp extends StatelessWidget {
  final Store<State> store;
  const MyApp({Key? key, required this.store}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return StoreProvider(
      store: store,
      child: MaterialApp(
        debugShowCheckedModeBanner: false,
        title: 'Flutter Demo',
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
        home: const HomePage(),
      ),
    );
  }
}

修改HomePage,不再创建Store,通过StoreProvider.of获取:

class HomePage extends StatelessWidget {
  const HomePage({Key? key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Center(
          child: Text('Flutter Redux Demo'),
        ),
      ),
      body: Column(children: [
        TextButton(
            onPressed: () {
              // 通过StoreProvider获取全局Store
              StoreProvider.of<State>(context).dispatch(const LoadPeopleAction());
            },
            child: const Center(child: Text('Load Persons'))),
        // 保持原有加载状态组件
        StoreConnector<State, bool>(
            converter: (store) => store.state.isLoading,
            builder: (context, isLoading) {
              if (isLoading) {
                return const CircularProgressIndicator();
              } else {
                return const SizedBox();
              }
            }),
        // 添加错误信息展示组件
        StoreConnector<State, Object?>(
            converter: (store) => store.state.error,
            builder: (context, error) {
              if (error != null) {
                return Padding(
                  padding: const EdgeInsets.all(8.0),
                  child: Text('加载失败: $error', style: TextStyle(color: Colors.red)),
                );
              }
              return const SizedBox();
            }),
        // 保持原有列表展示组件
        StoreConnector<State, Iterable<Person>?>(
            converter: (store) => store.state.fetchedPersons,
            builder: (context, people) {
              if (people == null) {
                return const SizedBox();
              } else {
                return Expanded(
                  child: ListView.builder(
                      itemCount: people.length,
                      itemBuilder: (context, index) {
                        final person = people.elementAt(index);
                        return ListTile(
                          title: Text(person.name),
                          subtitle: Text('${person.age} years old.'),
                        );
                      }),
                );
              }
            })
      ]),
    );
  }
}

2. 本地服务器地址适配模拟器

如果使用Android模拟器,127.0.0.1指向模拟器自身,无法访问电脑上的Live Server,需要替换为:

const apiUrl = 'http://10.0.2.2:5500/api/people.json';

iOS模拟器可以直接使用127.0.0.1或localhost。

真机测试时,需要将地址替换为电脑的局域网IP(比如192.168.1.100:5500),确保手机和电脑在同一WiFi下。

3. 验证Live Server可用性

在浏览器中访问http://127.0.0.1:5500/api/people.json,确认能正常返回JSON内容,否则检查Live Server的运行状态和文件路径是否正确。

4. 补充错误展示

添加错误信息展示组件后,可以直接看到请求失败的具体原因(比如网络错误、地址错误),方便进一步排查。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.20 12:09:20