我正在使用https://newsapi.org/API和Riverpod开发一个小的Flutter应用程序。我想让我的应用程序离线工作(当没有互联网连接)。我在何时/何处保存API响应以及在何处检索它时感到困惑。
主要。飞镖(编辑)
import 'package:flutter/material.dart';
import 'package:flutter_pace_stock_internship_task/model/article_model.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'providers/providers.dart';
import 'screens/home_screen.dart';
import 'utility/size_config.dart';
import 'package:hive_flutter/hive_flutter.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Hive.initFlutter();
Hive.registerAdapter(ArticleAdapter());
Hive.registerAdapter(SourceAdapter());
await Hive.openBox('myarticles');
runApp(
ProviderScope(
child: MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Flutter Demo',
themeMode: ThemeMode.dark,
darkTheme: ThemeData.dark(),
theme: ThemeData(
fontFamily: 'Roboto',
// scaffoldBackgroundColor: scaffoldBgColor,
),
home: const HomeScreen())),
);
}
article_model.dart
import 'package:hive/hive.dart';
part 'article_model.g.dart';
@HiveType(typeId: 0)
class Article {
Article({
this.source,
this.author,
this.title,
this.description,
this.url,
this.urlToImage,
this.publishedAt,
this.content,
});
@HiveField(0)
Source? source;
@HiveField(1)
String? author;
@HiveField(2)
String? title;
@HiveField(3)
String? description;
@HiveField(4)
String? url;
@HiveField(5)
String? urlToImage;
@HiveField(6)
String? publishedAt;
@HiveField(7)
String? content;
factory Article.fromJson(Map<String, dynamic> json) => Article(
source: Source.fromJson(json["source"]),
author: json["author"] ?? '',
title: json["title"] ?? '',
description: json["description"] ?? '',
url: json["url"] ?? '',
urlToImage: json["urlToImage"] ?? '',
publishedAt: json["publishedAt"] ?? '',
content: json["content"] ?? '',
);
Map<String, dynamic> toJson() => {
"source": source!.toJson(),
"author": author ?? '',
"title": title ?? '',
"description": description ?? '',
"url": url ?? '',
"urlToImage": urlToImage ?? '',
"publishedAt": publishedAt ?? '',
"content": content ?? '',
};
}
@HiveType(typeId: 1)
class Source {
Source({
this.id,
this.name,
});
@HiveField(0)
String? id;
@HiveField(1)
String? name;
factory Source.fromJson(Map<String, dynamic> json) => Source(
id: json["id"] ?? '',
name: json["name"] ?? '',
);
Map<String, dynamic> toJson() => {
"id": id ?? '',
"name": name ?? '',
};
}
api_service.dart
import 'dart:convert';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:http/http.dart' as http;
import '../model/article_model.dart';
class ApiService {
final String _endPoint =
'https://newsapi.org/v2/top-headlines?country=in&apiKey=YOUR_API_KEY';
Future<List<Article>> getArticles() async {
http.Response response = await http.get(Uri.parse(_endPoint));
if (response.statusCode == 200) {
final List result = jsonDecode(response.body)["articles"];
return result.map<Article>((e) => Article.fromJson(e)).toList();
} else {
throw Exception(response.reasonPhrase);
}
}
}
final apiProvider = Provider<ApiService>((ref) => ApiService());
我已经写了检查是否有互联网连接的逻辑在providers.dart.
providers.dart
import 'dart:convert';
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../model/article_model.dart';
import '../services/api_service.dart';
enum NetworkStatus {
on,
off,
}
class NetworkDetectorNotifier extends StateNotifier<NetworkStatus> {
late NetworkStatus newState;
NetworkDetectorNotifier() : super(NetworkStatus.off) {
Connectivity().onConnectivityChanged.listen((ConnectivityResult result) {
switch (result) {
case ConnectivityResult.wifi:
newState = NetworkStatus.on;
break;
case ConnectivityResult.mobile:
newState = NetworkStatus.on;
break;
case ConnectivityResult.none:
newState = NetworkStatus.off;
break;
case ConnectivityResult.bluetooth:
case ConnectivityResult.ethernet:
case ConnectivityResult.vpn:
}
if (newState != state) {
state = newState;
}
});
}
}
final networkCheckProvider =
StateNotifierProvider<NetworkDetectorNotifier, NetworkStatus>(
(_) => NetworkDetectorNotifier());
final articleProvider = FutureProvider<List<Article>>(
(ref) async => await ref.watch(apiProvider).getArticles());
home_screen.dart
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:intl/intl.dart';
import '../constant/constants.dart';
import '../providers/providers.dart';
import '../utility/size_config.dart';
import 'details_screen.dart';
class HomeScreen extends ConsumerWidget {
const HomeScreen({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
//SizeConfig().init(context);
final articles = ref.watch(articlesProviderNew);
return Scaffold(
backgroundColor: scaffoldBgColor,
appBar: AppBar(
backgroundColor: appBarColor,
centerTitle: true,
title: const Text('HEADLINES',
style: TextStyle(
fontSize: appBarTitleSize,
color: appBarTitleColor,
fontWeight: FontWeight.w700,
)),
),
body: articles.when(
data: ((articles) => ListView.builder(
// padding: const EdgeInsets.fromLTRB(16, 0.0, 16, 24),
itemCount: articles.length,
itemBuilder: (context, index) {
final article = articles[index];
final date = DateFormat('yy-mm-dd').parse(article.publishedAt!);
return Padding(
padding: const EdgeInsets.only(
top: 16.0, left: 16.0, right: 16.0, bottom: 24.0),
child: SizedBox(
height: SizeConfig.screenHeight * .35,
width: SizeConfig.screenWidth * .75,
child: InkWell(
onTap: (() =>
Navigator.of(context).push(MaterialPageRoute(
builder: ((context) => DetailsScreen(
article: article,
))))),
child: Card(
shape: const RoundedRectangleBorder(),
child: GridTile(
footer: GridTileBar(
backgroundColor: Colors.black.withOpacity(.5),
title: Text(
article.title!,
style: const TextStyle(
fontSize: articleTitleTextSize,
color: articleTitleTextColor,
),
),
subtitle: Text(
'${article.author!}tttt$date',
style: const TextStyle(
fontSize: authorTextSize,
color: authorTextColor,
),
),
),
child: ClipRRect(
borderRadius: BorderRadius.circular(10.0),
child: CachedNetworkImage(
fit: BoxFit.cover,
errorWidget: (context, url, error) =>
Image.asset('assets/images/no_image.png'),
placeholder: (context, url) => const Center(
child: CircularProgressIndicator()),
imageUrl:
articles.elementAt(index).urlToImage!),
),
),
),
),
),
);
},
)),
error: ((error, stackTrace) {
print('Error : $errorn$stackTrace');
return Text('Error : $errorn$stackTrace');
}),
loading: () => const Center(
child: CircularProgressIndicator(),
),
),
);
}
}
details_screen.dart
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import '../constant/constants.dart';
import '../model/article_model.dart';
import '../utility/size_config.dart';
class DetailsScreen extends StatelessWidget {
const DetailsScreen({Key? key, required this.article}) : super(key: key);
final Article article;
@override
Widget build(BuildContext context) {
return Stack(
children: [
CachedNetworkImage(
height: SizeConfig.screenHeight,
width: SizeConfig.screenWidth,
fit: BoxFit.cover,
errorWidget: (context, url, error) =>
Image.asset('assets/images/no_image.png'),
placeholder: (context, url) =>
const Center(child: CircularProgressIndicator()),
imageUrl: article.urlToImage!),
Scaffold(
backgroundColor: Colors.transparent,
appBar: AppBar(
elevation: 0.0,
backgroundColor: Colors.transparent,
leading: Container(
height: 42.0,
width: 42.0,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: const Color.fromRGBO(0, 0, 0, 1).withOpacity(.3)),
child: IconButton(
icon: const Icon(
Icons.arrow_back,
size: 42.0,
),
onPressed: () => Navigator.of(context).pop(),
),
)),
body: Padding(
padding: const EdgeInsets.all(leftPadding2),
child: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Text(
article.title!,
style: const TextStyle(
fontSize: appBarTitleSize, color: articleTitleTextColor),
),
const SizedBox(
height: vertPadding,
),
Row(
//mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Text(article.author!,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
fontSize: articleTitleTextSize,
color: articleTitleTextColor)),
),
const SizedBox(
width: 10.0,
),
Expanded(
child: Text(article.publishedAt!,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
fontSize: articleTitleTextSize,
color: articleTitleTextColor)),
),
],
),
const SizedBox(
height: vertPadding2,
),
Text(article.content!,
style: const TextStyle(
fontSize: articleContentSize, color: authorTextColor))
],
),
),
),
],
);
}
}
谁能告诉我如何使我的应用程序离线工作?
编辑-:以下代码我用于保存和检索API数据,但我在检索时得到错误,即类型'列表'不是类型'FutureOr'的子类型。
final articlesProviderNew = FutureProvider<List<Article>>((ref) async {
var articleBox = Hive.box('myarticles');
var network = ref.watch(networkCheckProvider);
switch (network) {
case NetworkStatus.on:
{
List<Article> articles = await ref.watch(apiProvider).getArticles();
var articleLS = articleBox.get('articleLS');
if (articleLS == null || articleLS.isEmpty) {
await articleBox.put('articleLS', articles);
}
return articles;
}
case NetworkStatus.off:
{
var articleLS = articleBox.get('articleLS');
return articleLS;
}
}
});
首先你需要检查:这是你第一次强迫用户上网。所有的数据将第一次存储在本地内存中,然后如果用户没有互联网,那么你可以从本地内存中获取数据。
class NetoworkCheck extends ConsumerWidget {
const NetoworkCheck({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
SizeConfig().init(context);
var network = ref.watch(networkCheckProvider);
if (network == NetworkStatus.off) {
return const NoNetworkScreen();
}
return const HomeScreen();
}
}
你可以在这里查看然后把存储数据的代码放在这里
if (network == NetworkStatus.off) {
store all data
return const NoNetworkScreen();
}
不要忘记使用async/await,因为它需要一些时间来存储数据