如果在获取图像时关闭网络,Future Builder 中的空白区域会颤动
Flutter Empty Spaces in Future Builder if you turned off network while fetching images
你好,我有一个简单的应用程序,可以从网络服务器获取图像,然后在网格视图生成器中显示它们
我想做的是拉起重建所有图像请注意,我已经实现了 RefreshIndicator() 并且它的 OnRefresh 功能在数据库中发生更改时可以正常工作,它会将新图像添加到观点,
我也希望它重建所有图像,因为想象一下我的情况:
你打开了应用程序,未来的构建器获取了数据,现在它正在显示它们,但是当它显示它们时你突然断开了与互联网的连接,这会给你留下空白空间(它应该是图像,但现在它们是空白的)所以我想做的是在拉起时重建这些图像,这样如果用户发现任何丢失的图像,他可以拉起刷新页面并再次重建所有图像
这是我的代码,只是 RefreshCompanies() 需要修改
import 'dart:convert';
import 'package:app/exceptions/connection_error.dart';
import 'package:app/exceptions/empty_db.dart';
import 'package:app/reusable_widgets/interfaces/Companies/Companies_interface.dart';
import 'package:app/reusable_widgets/interfaces/LoadingIndicator.dart';
import 'package:app/reusable_widgets/interfaces/Main_Layout.dart';
import 'package:app/reusable_widgets/logic/Check_version.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
import 'package:http/http.dart' as http;
import 'Categories.dart';
import 'package:app/reusable_widgets/globals.dart';
class Companies {
final int id;
final String name;
final String companyLogo;
Companies({this.id, this.name, this.companyLogo});
factory Companies.fromJson(Map<String, dynamic> json) {
return Companies(
id: json['id'],
name: json['name'],
companyLogo: json['company_logo'],
);
}
}
Future<List<Companies>> fetchCompanies() async {
final response = await http.get('$webSiteUrl/company/api/fetch');
if (response.statusCode == 200) {
// If the server did return a 200 OK response,
// then parse the JSON.
return parseCompanies(response.body);
} else {
// If the server did not return a 200 OK response,
// then throw an exception.
throw Exception('Failed to load the companies');
}
}
List<Companies> parseCompanies(String responseBody) {
final parsed = json.decode(responseBody).cast<Map<String, dynamic>>();
return parsed.map<Companies>((json) => Companies.fromJson(json)).toList();
}
class CompaniesPage extends StatefulWidget{
@override
_CompaniesState createState() => _CompaniesState();
}
class _CompaniesState extends State<CompaniesPage> {
Future<List<Companies>> _companies;
var refreshKey = GlobalKey<RefreshIndicatorState>();
@override
void initState() {
super.initState();
checkVersion(context);
_companies = fetchCompanies();
}
Future<Null> refreshCompanies() async{
await DefaultCacheManager().emptyCache();
await Future.delayed(Duration(seconds: 2)).then((value) =>
setState(() {
_companies = fetchCompanies();
})
);
}
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: FutureBuilder<List<Companies>>(
future: _companies,
builder: (context, snapshot) {
if (snapshot.hasData) {
List<Companies> companies = snapshot.data;
if(companies.length >= 1){
return MainLayout(
RefreshIndicator(
onRefresh: refreshCompanies,
key: refreshKey,
child: GridView.count(
crossAxisCount: 2 ,
children: List.generate(companies.length, (index) {
return GestureDetector(
onTap: () => {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => Categories(companies[index].id, companies[index].name)),
)},
child: CompaniesInterface(companies[index].id , companies[index].name , companies[index].companyLogo),
);
}),
),
),
);
}else{
return EmptyDataBase();
}
} else if (snapshot.hasError) {
return ConnectionError();
}
// By default, show a loading spinner.
return LoadingIndicator();
},
),
),
);
}
}
我的公司界面
class CompaniesInterface extends StatelessWidget{
final companyId;
final companyName;
final companyLogo;
CompaniesInterface(this.companyId , this.companyName ,this.companyLogo);
@override
Widget build(BuildContext context) {
return Align(
child : Container(
margin: EdgeInsets.all(20),
height: DeviceInformation(context).height * 0.7,
width: DeviceInformation(context).width * 0.9,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
shape: BoxShape.rectangle,
image: DecorationImage(
image: NetworkImage('$webSiteUrl$companyLogo'),
fit: BoxFit.fill,
),
),
),
);
}
}
在这里你可以找到我所说的空格的意思
正如您从代码中看到的那样,我尝试调用 EmptyCache() 但它没有重建网格视图
建议的解决方案:如果您可以找到以编程方式热重载应用程序的功能,那么热重载正确地完成了所需的事情,那么我认为这将解决问题
更新:
对于将来会遇到同样问题的任何人
恐怕我的问题没有直接的答案所以我给了我个人观点最好的
FutureBuilder
只会 运行 一次。在 Future
跟踪完成后,它将不再重建。您必须调用 setState()
或将其转换为 Stream
,每当刷新数据时添加新数据。
不确定这对您是否有用,但也许您只有在有活动连接时才能获取图像。 https://pub.dev/packages/connectivity
伪代码:
Stream<List<Companies>> $ companiesStream = $connectivitySteam()
.filter(x => x.hadInternet)
.take(1)
.switchMap(x => fetchCompanies)
像这样的东西只会在有互联网连接时获取公司。
不要在没有互联网连接的情况下空 space,而是尝试使用 cached_network_image 作为图像小部件,它会为您提供更多控件,例如占位符(如果没有连接,您仍然可以显示正在加载的图像):
CachedNetworkImage(
imageUrl: "http://via.placeholder.com/200x150",
imageBuilder: (context, imageProvider) => Container(
decoration: BoxDecoration(
image: DecorationImage(
image: imageProvider,
fit: BoxFit.cover,
colorFilter:
ColorFilter.mode(Colors.red, BlendMode.colorBurn)),
),
),
placeholder: (context, url) => CircularProgressIndicator(),
errorWidget: (context, url, error) => Icon(Icons.error),
),
或者如果没有连接,您甚至可以使用 flutter_advanced_networkimage:
刷新您想要的每张图片
TransitionToImage(
image: AdvancedNetworkImage(url,
loadedCallback: () {
print('It works!');
},
loadFailedCallback: () {
print('Oh, no!');
},
loadingProgress: (double progress) {
print('Now Loading: $progress');
},
),
loadingWidgetBuilder: (_, double progress, __) => Text(progress.toString()),
fit: BoxFit.contain,
placeholder: const Icon(Icons.refresh),
width: 400.0,
height: 300.0,
enableRefresh: true, // <-- Refresh button
);
你好,我有一个简单的应用程序,可以从网络服务器获取图像,然后在网格视图生成器中显示它们
我想做的是拉起重建所有图像请注意,我已经实现了 RefreshIndicator() 并且它的 OnRefresh 功能在数据库中发生更改时可以正常工作,它会将新图像添加到观点,
我也希望它重建所有图像,因为想象一下我的情况:
你打开了应用程序,未来的构建器获取了数据,现在它正在显示它们,但是当它显示它们时你突然断开了与互联网的连接,这会给你留下空白空间(它应该是图像,但现在它们是空白的)所以我想做的是在拉起时重建这些图像,这样如果用户发现任何丢失的图像,他可以拉起刷新页面并再次重建所有图像
这是我的代码,只是 RefreshCompanies() 需要修改
import 'dart:convert';
import 'package:app/exceptions/connection_error.dart';
import 'package:app/exceptions/empty_db.dart';
import 'package:app/reusable_widgets/interfaces/Companies/Companies_interface.dart';
import 'package:app/reusable_widgets/interfaces/LoadingIndicator.dart';
import 'package:app/reusable_widgets/interfaces/Main_Layout.dart';
import 'package:app/reusable_widgets/logic/Check_version.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
import 'package:http/http.dart' as http;
import 'Categories.dart';
import 'package:app/reusable_widgets/globals.dart';
class Companies {
final int id;
final String name;
final String companyLogo;
Companies({this.id, this.name, this.companyLogo});
factory Companies.fromJson(Map<String, dynamic> json) {
return Companies(
id: json['id'],
name: json['name'],
companyLogo: json['company_logo'],
);
}
}
Future<List<Companies>> fetchCompanies() async {
final response = await http.get('$webSiteUrl/company/api/fetch');
if (response.statusCode == 200) {
// If the server did return a 200 OK response,
// then parse the JSON.
return parseCompanies(response.body);
} else {
// If the server did not return a 200 OK response,
// then throw an exception.
throw Exception('Failed to load the companies');
}
}
List<Companies> parseCompanies(String responseBody) {
final parsed = json.decode(responseBody).cast<Map<String, dynamic>>();
return parsed.map<Companies>((json) => Companies.fromJson(json)).toList();
}
class CompaniesPage extends StatefulWidget{
@override
_CompaniesState createState() => _CompaniesState();
}
class _CompaniesState extends State<CompaniesPage> {
Future<List<Companies>> _companies;
var refreshKey = GlobalKey<RefreshIndicatorState>();
@override
void initState() {
super.initState();
checkVersion(context);
_companies = fetchCompanies();
}
Future<Null> refreshCompanies() async{
await DefaultCacheManager().emptyCache();
await Future.delayed(Duration(seconds: 2)).then((value) =>
setState(() {
_companies = fetchCompanies();
})
);
}
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: FutureBuilder<List<Companies>>(
future: _companies,
builder: (context, snapshot) {
if (snapshot.hasData) {
List<Companies> companies = snapshot.data;
if(companies.length >= 1){
return MainLayout(
RefreshIndicator(
onRefresh: refreshCompanies,
key: refreshKey,
child: GridView.count(
crossAxisCount: 2 ,
children: List.generate(companies.length, (index) {
return GestureDetector(
onTap: () => {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => Categories(companies[index].id, companies[index].name)),
)},
child: CompaniesInterface(companies[index].id , companies[index].name , companies[index].companyLogo),
);
}),
),
),
);
}else{
return EmptyDataBase();
}
} else if (snapshot.hasError) {
return ConnectionError();
}
// By default, show a loading spinner.
return LoadingIndicator();
},
),
),
);
}
}
我的公司界面
class CompaniesInterface extends StatelessWidget{
final companyId;
final companyName;
final companyLogo;
CompaniesInterface(this.companyId , this.companyName ,this.companyLogo);
@override
Widget build(BuildContext context) {
return Align(
child : Container(
margin: EdgeInsets.all(20),
height: DeviceInformation(context).height * 0.7,
width: DeviceInformation(context).width * 0.9,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
shape: BoxShape.rectangle,
image: DecorationImage(
image: NetworkImage('$webSiteUrl$companyLogo'),
fit: BoxFit.fill,
),
),
),
);
}
}
在这里你可以找到我所说的空格的意思
正如您从代码中看到的那样,我尝试调用 EmptyCache() 但它没有重建网格视图
建议的解决方案:如果您可以找到以编程方式热重载应用程序的功能,那么热重载正确地完成了所需的事情,那么我认为这将解决问题
更新: 对于将来会遇到同样问题的任何人 恐怕我的问题没有直接的答案所以我给了我个人观点最好的
FutureBuilder
只会 运行 一次。在 Future
跟踪完成后,它将不再重建。您必须调用 setState()
或将其转换为 Stream
,每当刷新数据时添加新数据。
不确定这对您是否有用,但也许您只有在有活动连接时才能获取图像。 https://pub.dev/packages/connectivity
伪代码:
Stream<List<Companies>> $ companiesStream = $connectivitySteam()
.filter(x => x.hadInternet)
.take(1)
.switchMap(x => fetchCompanies)
像这样的东西只会在有互联网连接时获取公司。
不要在没有互联网连接的情况下空 space,而是尝试使用 cached_network_image 作为图像小部件,它会为您提供更多控件,例如占位符(如果没有连接,您仍然可以显示正在加载的图像):
CachedNetworkImage(
imageUrl: "http://via.placeholder.com/200x150",
imageBuilder: (context, imageProvider) => Container(
decoration: BoxDecoration(
image: DecorationImage(
image: imageProvider,
fit: BoxFit.cover,
colorFilter:
ColorFilter.mode(Colors.red, BlendMode.colorBurn)),
),
),
placeholder: (context, url) => CircularProgressIndicator(),
errorWidget: (context, url, error) => Icon(Icons.error),
),
或者如果没有连接,您甚至可以使用 flutter_advanced_networkimage:
刷新您想要的每张图片TransitionToImage(
image: AdvancedNetworkImage(url,
loadedCallback: () {
print('It works!');
},
loadFailedCallback: () {
print('Oh, no!');
},
loadingProgress: (double progress) {
print('Now Loading: $progress');
},
),
loadingWidgetBuilder: (_, double progress, __) => Text(progress.toString()),
fit: BoxFit.contain,
placeholder: const Icon(Icons.refresh),
width: 400.0,
height: 300.0,
enableRefresh: true, // <-- Refresh button
);