我正在使用https://newsapi.org/ API和Riverpod开发一个小型Flutter应用程序。我想让我的应用程序离线工作(当没有互联网连接时)。我对何时/何地保存API响应以及在哪里检索它感到困惑。
main.dart
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'providers/providers.dart';
import 'screens/home_screen.dart';
import 'utility/size_config.dart';
void main() {
runApp(
ProviderScope(
child: MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Flutter Demo',
themeMode: ThemeMode.dark,
darkTheme: ThemeData.dark(),
theme: ThemeData(
fontFamily: 'Roboto',
// scaffoldBackgroundColor: scaffoldBgColor,
),
home: const NetoworkCheck())),
);
}
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();
}
}
class NoNetworkScreen extends StatelessWidget {
const NoNetworkScreen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return const Scaffold(
body: SafeArea(
child: Center(
child: Text('No internet connection, please try again..!!'),
)),
);
}
}
产品_型号.省道
import 'dart:convert';
class Article {
Article({
required this.source,
this.author,
this.title,
this.description,
this.url,
this.urlToImage,
this.publishedAt,
this.content,
});
Source? source;
String? author;
String? title;
String? description;
String? url;
String? urlToImage;
String? publishedAt;
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 ?? '',
};
}
class Source {
Source({
this.id,
this.name,
});
String? id;
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_服务.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内部是否存在Internet连接的逻辑。
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 sharedPrefs = FutureProvider<SharedPreferences>(
(_) async => await SharedPreferences.getInstance());
final networkCheckProvider =
StateNotifierProvider<NetworkDetectorNotifier, NetworkStatus>(
(_) => NetworkDetectorNotifier());
final articleProvider = FutureProvider<List<Article>>(
(ref) async => await ref.watch(apiProvider).getArticles());
final articleProviderNew = FutureProvider<List<Article>>((ref) async {
final prefs = ref.watch(sharedPrefs);
final nwtrk = ref.watch(networkCheckProvider);
final articles = await ref.watch(apiProvider).getArticles();
switch (nwtrk) {
case NetworkStatus.on:
//await prefs.value!.setString('articles', jsonEncode(articles));
return articles;
case NetworkStatus.off:
final data = prefs.value!.getString('articles');
final List<dynamic> jsonData = jsonDecode(data!);
return jsonData.map<Article>((e) => Article.fromJson(e)).toList();
}
});
主屏幕.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(articleProvider);
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!}\t\t\t\t$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 : $error\n$stackTrace');
return Text('Error : $error\n$stackTrace');
}),
loading: () => const Center(
child: CircularProgressIndicator(),
),
),
);
}
}
细节_屏幕.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))
],
),
),
),
],
);
}
}
有人能告诉我如何让我的应用离线工作吗?
2条答案
按热度按时间zf9nrax11#
首先你需要检查:有史以来第一次你需要强迫用户有互联网。所有的数据,他们将存储在本地内存的第一次,然后如果用户将不会有互联网,那么你可以从本地内存获得数据。
你可以在这里打勾,然后把你的代码放在这里存储数据
别忘了使用async/await,大多数情况下,它需要一些时间来存储数据
dm7nw8vv2#
要在Flutter中本地保存API数据,您可以使用SQLite等本地数据库或简单的文件来存储数据。以下是在Flutter应用中本地保存API数据的步骤:
解析API响应并提取要保存的数据。
打开到本地数据库(SQLite)的连接或创建文件以存储数据。
将数据保存在本地数据库或文件中。
如果要检索数据,请打开与本地数据库的连接或读取文件,然后从中检索数据。
使用检索到的数据填充您的应用。
您可以使用sqflite这样的库来管理本地SQLite数据库并对其执行CRUD(创建、读取、更新、删除)操作。
你也可以使用flutter_secure_storage这样的插件将数据存储在应用的本地安全存储中。
若要检查设备是否已连接Internet,可以使用连接包。如果没有Internet连接,可以从本地数据库或文件检索数据,并使用它填充应用程序。如果有Internet连接,可以向API发出网络请求,并使用最新数据更新本地数据库或文件。