dart 如何在Flutter中本地保存API数据,以便应用程序可以在没有互联网连接的情况下工作?

ruoxqz4g  于 2023-01-10  发布在  Flutter
关注(0)|答案(2)|浏览(168)

我正在使用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))
              ],
            ),
          ),
        ),
      ],
    );
  }
}

有人能告诉我如何让我的应用离线工作吗?

zf9nrax1

zf9nrax11#

首先你需要检查:有史以来第一次你需要强迫用户有互联网。所有的数据,他们将存储在本地内存的第一次,然后如果用户将不会有互联网,那么你可以从本地内存获得数据。

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,大多数情况下,它需要一些时间来存储数据

dm7nw8vv

dm7nw8vv2#

要在Flutter中本地保存API数据,您可以使用SQLite等本地数据库或简单的文件来存储数据。以下是在Flutter应用中本地保存API数据的步骤:
解析API响应并提取要保存的数据。
打开到本地数据库(SQLite)的连接或创建文件以存储数据。
将数据保存在本地数据库或文件中。
如果要检索数据,请打开与本地数据库的连接或读取文件,然后从中检索数据。
使用检索到的数据填充您的应用。
您可以使用sqflite这样的库来管理本地SQLite数据库并对其执行CRUD(创建、读取、更新、删除)操作。
你也可以使用flutter_secure_storage这样的插件将数据存储在应用的本地安全存储中。
若要检查设备是否已连接Internet,可以使用连接包。如果没有Internet连接,可以从本地数据库或文件检索数据,并使用它填充应用程序。如果有Internet连接,可以向API发出网络请求,并使用最新数据更新本地数据库或文件。

相关问题