我正在尝试使用 Hacker News API 创建一个网络博客,但每次我尝试运行时,都会收到这个令人困惑的错误,但我不明白。问chatgpt,它给了我一个解决方案,但我仍然遇到同样的错误。我正在粘贴所有必要的课程。
首先这是我的数据库类。
import 'dart:io';
import 'package:new_flutter/src/models/item_model.dart';
import 'package:path/path.dart';
import 'package:path_provider/path_provider.dart';
import 'package:sqflite/sqflite.dart';
final newsDbProv2 = NewsDbProv2();
class NewsDbProv2 {
static const _dbName = "items.db";
static const _dbVersion = 1;
static const _tableName = "Items";
Database? db;
/// INITIALIZING THE DATABASE......
Future<void> init() async {
// if (db != null) return;
if (db == null) {
try {
Directory documentsDirectory = await getApplicationDocumentsDirectory();
final path = join(documentsDirectory.path, _dbName);
db = await openDatabase(path,
version: _dbVersion, onCreate: createTable);
} catch (e) {
print('Error initializing database: $e');
}
}
}
///CREATING THE DATABASE TABLE........
Future<void> createTable(Database newDb, int version) async {
try {
await newDb.execute("""
CREATE TABLE $_tableName
(
id INTEGER PRIMARY KEY,
type TEXT,
by TEXT,
time INTEGER,
text TEXT,
parent INTEGER,
kids BLOB,
dead INTEGER,
deleted INTEGER,
url TEXT,
score INTEGER,
title TEXT,
descendants INTEGER
)
""");
} catch (e) {
print('Error creating table: $e');
}
}
///Fetching an ITEM by ID from the DATABASE.....
Future<ItemModel?> fetchItem(int id) async {
try {
if (db == null) await init();
final maps = await db!.query(
_tableName,
columns: null,
where: "id = ?",
whereArgs: [id],
);
if (maps.isNotEmpty) {
return ItemModel.fromDB(maps.first);
}
} catch (e) {
print('Error fetching item from the database: $e');
}
return null;
}
Future<int> addItem(ItemModel item) async {
try {
if (db == null) await init();
return await db!.transaction((txn) async {
return await txn.insert(
// return db!.insert(
_tableName,
item.toMapForDb(),
conflictAlgorithm: ConflictAlgorithm.ignore,
);
});
} catch (e) {
print('Error adding item to database: $e');
return 0;
}
}
// Future<int> addItem(ItemModel item) async {
// return await db!.transaction((txn) async {
// return await txn.insert(
// // return db!.insert(
// _tableName,
// item.toMapForDb(),
// conflictAlgorithm: ConflictAlgorithm.ignore,
// );
// });
// }
}
// Future<List<int>> fetchTopIds() {
// // TODO: implement fetchTopIds
// throw UnimplementedError();
// }
这是api类
import 'dart:convert';
import 'package:http/http.dart' show Client;
import 'package:new_flutter/src/models/item_model.dart';
const rootDomain = 'https://hacker-news.firebaseio.com/v0';
class NewsApiProv2 {
Client client = Client();
/// Fetching Top story IDs from the API.......
Future<List<int>> fetchTopIds() async {
try {
final response = await client
.get(Uri.parse('$rootDomain/topstories.json'))
.timeout(const Duration(seconds: 10));
if (response.statusCode == 200) {
final ids = json.decode(response.body);
return ids.cast<int>();
} else {
print("Error: Failed to fetch top IDs. Status: ${response.statusCode}");
}
} catch (e) {
print('Exception during fetchTopIds: $e');
}
return [];
}
/// Fetching a specific Story/ITEM by ID........
Future<ItemModel?> fetchItem(int id) async {
try {
final response = await client
.get(Uri.parse('$rootDomain/item/$id.json'))
.timeout(const Duration(seconds: 10));
if (response.statusCode == 200) {
final parsedJson = json.decode(response.body);
if (parsedJson == null || parsedJson.isEmpty) {
print('Empty or null response for item with id $id');
return null;
}
return ItemModel.fromJson(parsedJson);
} else {
print(
'Failed to fetch item with id $id, Status Code: ${response.statusCode}');
throw Exception('Failed to load item');
}
} catch (e) {
print('Exception during fetchItem: $e');
}
return null;
}
}
这是存储库类:
import 'package:new_flutter/src/models/item_model.dart';
import 'package:new_flutter/src/resources/new_resource_dir/news_api_prov2.dart';
import 'package:new_flutter/src/resources/new_resource_dir/news_db_prov_2.dart';
class RepositoryProv2 {
/// Initialize instances of the providers
final NewsApiProv2 _newsApiProvider = NewsApiProv2();
final NewsDbProv2 _newsDbProvider = NewsDbProv2();
/// Fetch the top story IDs from the API
Future<List<int>> fetchTopIds() async {
try {
return await _newsApiProvider.fetchTopIds();
} catch (e) {
print("Error fetching top IDs: $e");
return [];
}
}
/// Fetch an individual item from either API or Database (cache)
Future<ItemModel?> fetchItems(int id) async {
try {
/// First try fetching the item from the database (cache)
ItemModel? item = await _newsDbProvider.fetchItem(id);
/// If the item is not found in the database, fetch from the API
if (item == null) {
item = await _newsApiProvider.fetchItem(id);
/// If the item was found, save it in the database (cache)
if (item != null) {
await _newsDbProvider.addItem(item);
}
}
return item;
} catch (e) {
print("Error fetching item: $e");
return null;
}
}
}
这是我的集体课:
import 'package:flutter/cupertino.dart';
import 'package:new_flutter/src/models/item_model.dart';
import 'package:new_flutter/src/resources/new_resource_dir/repository_prov2.dart';
class NewsBloc extends ChangeNotifier {
final RepositoryProv2 _prov2 = RepositoryProv2();
List<int> _topIds = [];
final List<ItemModel> _items = [];
bool _isLoading = false;
List<int> get topIds => _topIds;
List<ItemModel> get items => _items;
bool get isLoading => _isLoading;
///Fetch Top IDs from the API
Future<void> fetchTopIds() async {
_isLoading = true;
notifyListeners(); //Notify UI to update
try {
_topIds = await _prov2.fetchTopIds();
} catch (e) {
print('Error fetching top IDs: $e');
} finally {
_isLoading = false;
notifyListeners();
}
}
/// Fetching ITEMS by IDs
Future<ItemModel?> fetchItem(int id) async {
_isLoading = true;
notifyListeners();
try {
final item = await _prov2.fetchItems(id);
if (item != null) {
_items.add(item);
}
} catch (e) {
print('Error fetching item: $e');
} finally {
WidgetsBinding.instance.addPostFrameCallback((_) {
_isLoading = false;
notifyListeners();
});
}
return null;
}
}
这是app.dart:
import 'package:flutter/material.dart';
import 'package:new_flutter/src/blocs/latest_bloc_provider/news_bloc.dart';
import 'package:new_flutter/src/screens/news_list.dart';
import 'package:provider/provider.dart';
class App extends StatelessWidget {
const App({super.key});
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (context) => NewsBloc(),
child: MaterialApp(
title: 'News!',
home: NewsList(),
),
);
}
}
这是App.dart
import 'package:flutter/material.dart';
import 'package:new_flutter/src/blocs/latest_bloc_provider/news_bloc.dart';
import 'package:new_flutter/src/widgets/news_list_tile.dart';
import 'package:provider/provider.dart';
class NewsList extends StatelessWidget {
const NewsList({super.key});
@override
Widget build(BuildContext context) {
final bloc = Provider.of<NewsBloc>(context);
///Fetch Top IDs when the screen loads
if (bloc.topIds.isEmpty) {
bloc.fetchTopIds();
}
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
backgroundColor: Colors.blue,
elevation: 5,
title: const Text(
'Top News',
style: TextStyle(
color: Colors.white,
),
),
),
body: buildList(bloc),
);
}
Widget buildList(NewsBloc bloc) {
return Consumer<NewsBloc>(
builder: (context, bloc, child) {
if (bloc.isLoading) {
return Center(
child: CircularProgressIndicator(color: Colors.blue),
);
}
return ListView.builder(
itemCount: bloc.topIds.length,
itemBuilder: (context, int index) {
return NewsListTile(
itemId: bloc.topIds[index],
);
},
);
},
);
}
}
这是新闻列表图块类:
import 'package:flutter/material.dart';
import 'package:new_flutter/src/models/item_model.dart';
import 'package:provider/provider.dart';
import '../blocs/latest_bloc_provider/news_bloc.dart';
class NewsListTile extends StatelessWidget {
final int? itemId;
const NewsListTile({super.key, this.itemId});
@override
Widget build(BuildContext context) {
final bloc = Provider.of<NewsBloc>(context);
return FutureBuilder<ItemModel?>(
future: bloc.fetchItem(itemId!),
builder: (context, snapshot) {
// if (!snapshot.hasData) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Text('Still loading item $itemId');
}
if (snapshot.hasError) {
return Text('Error loading item $itemId: ${snapshot.error}');
}
if (!snapshot.hasData) {
return Text('No data available');
}
return Text(snapshot.data!.title);
},
);
}
}
我收到的错误令人困惑,就是这样:
======== Exception caught by foundation library ====================================================
The following assertion was thrown while dispatching notifications for NewsBloc:
setState() or markNeedsBuild() called during build.
This _InheritedProviderScope<NewsBloc?> widget cannot be marked as needing to build because the framework is already in the process of building widgets. A widget can be marked as needing to be built during the build phase only if one of its ancestors is currently building. This exception is allowed because the framework builds parent widgets before children, which means a dirty descendant will always be built. Otherwise, the framework might not visit this widget during this build phase.
The widget on which setState() or markNeedsBuild() was called was: _InheritedProviderScope<NewsBloc?>
value: Instance of 'NewsBloc'
listening to value
The widget which was currently being built when the offending call was made was: NewsListTile
dirty
dependencies: [_InheritedProviderScope<NewsBloc?>]
When the exception was thrown, this was the stack:
#0 Element.markNeedsBuild.<anonymous closure> (package:flutter/src/widgets/framework.dart:5177:9)
#1 Element.markNeedsBuild (package:flutter/src/widgets/framework.dart:5189:6)
#2 _InheritedProviderScopeElement.markNeedsNotifyDependents (package:provider/src/inherited_provider.dart:577:5)
#3 ChangeNotifier.notifyListeners (package:flutter/src/foundation/change_notifier.dart:437:24)
#4 NewsBloc.fetchItem (package:new_flutter/src/blocs/latest_bloc_provider/news_bloc.dart:36:5)
#5 NewsListTile.build (package:new_flutter/src/widgets/news_list_tile.dart:17:20)
#6 StatelessElement.build (package:flutter/src/widgets/framework.dart:5687:49)
#7 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5617:15)
The NewsBloc sending notification was: Instance of 'NewsBloc'
====================================================================================================
D/EGL_emulation(13392): app_time_stats: avg=1797.18ms min=1797.18ms max=1797.18ms count=1
I/flutter (13392): Warning database has been locked for 0:00:10.000000. Make sure you always use the transaction object for database operations during a transaction
I/flutter (13392): Warning database has been locked for 0:00:10.000000. Make sure you always use the transaction object for database operations during a transaction
I/flutter (13392): Warning database has been locked for 0:00:10.000000. Make sure you always use the transaction object for database operations during a transaction
I/flutter (13392): Warning database has been locked for 0:00:10.000000. Make sure you always use the transaction object for database operations during a transaction
I/flutter (13392): Warning database has been locked for 0:00:10.000000. Make sure you always use the transaction object for database operations during a transaction
I/flutter (13392): Warning database has been locked for 0:00:10.000000. Make sure you always use the transaction object for database operations during a transaction
根据
#4 NewsBloc.fetchItem(包:new_flutter/src/blocs/latest_bloc_provider/news_bloc.dart:36:5)
错误发生在bloc类
NewsBloc
第36行,将notifyListeners()
包裹在WidgetsBinding.instance.addPostFrameCallback
中可能会解决这个问题。
/// Fetching ITEMS by IDs
Future<ItemModel?> fetchItem(int id) async {
_isLoading = true;
WidgetsBinding.instance.addPostFrameCallback((_) {
notifyListeners();
});
try {
final item = await _prov2.fetchItems(id);
if (item != null) {
_items.add(item);
}
} catch (e) {
print('Error fetching item: $e');
} finally {
WidgetsBinding.instance.addPostFrameCallback((_) {
_isLoading = false;
notifyListeners();
});
}
return null;
}