I have a future builder which fetches an API
Everything works fine but I have an option to delete an item from the list.
When I delete an item the list should get refreshed or the future function must be called.
My code
class ViewPlans extends StatefulWidget {
static const id = 'ViewPlans';
#override
_ViewPlansState createState() => _ViewPlansState();
}
class _ViewPlansState extends State<ViewPlans> {
PlannerService _plannerService = new PlannerService();
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
body: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [Color(gradStartColor), Color(gradEndColor)])),
child: FutureBuilder(
future: _plannerService.getPlans(),
builder: (context, snapshot) {
var plans = snapshot.data;
if (plans == 'empty' || plans == null) {
return Center(
child: CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
));
}
return RefreshIndicator(
onRefresh: _plannerService.getPlans,
child: ListView.separated(
physics: BouncingScrollPhysics(),
itemCount: plans.content.length,
padding: EdgeInsets.symmetric(horizontal: 30, vertical: 30),
separatorBuilder: (context, index) => null,
itemBuilder: (context, index) {
return Container(
height: 100,
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 10),
child: Material(
elevation: 2,
shadowColor: Colors.white,
color: Colors.white,
borderRadius: BorderRadius.circular(10),
child: Center(
child: ListTile(
title: Text(
plans.content[index].task[0].toUpperCase() +
plans.content[index].task.substring(1),
style: TextStyle(
fontSize: 22,
fontFamily: agne,
color: Color(gradStartColor)),
),
trailing: IconButton(
icon: Icon(
Icons.delete,
color: Color(gradStartColor),
),
onPressed: () {
_plannerService
.deletePlan(plans.content[index].id)
.then((value) => handleDelete(value));
},
splashRadius: 25,
),
),
),
),
),
);
},
),
);
},
),
),
),
);
}
handleDelete(value) {
if (value == "deleted") {
Fluttertoast.showToast(
msg: 'Task deleted successfully',
fontSize: 20,
textColor: Colors.white,
toastLength: Toast.LENGTH_SHORT,
backgroundColor: Colors.black);
} else {
Fluttertoast.showToast(
msg: 'Task deletion failed',
fontSize: 20,
textColor: Colors.white,
toastLength: Toast.LENGTH_SHORT,
backgroundColor: Colors.black);
}
}
}
In this code, I can't even see the refresh indicator.
give a solution please thanks in advance
A quick solution is to use the setState((){}) function after the completion of delete operation. This will recall the build function, and it will refresh your widget tree.
You can create future variable and assign it to _plannerService.getPlans(). Use it in your future builder instead of _plannerService.getPlans(). Change onRefresh to setState((){future = _plannerService.getPlans()}). This code will rebuild your FutureBuilder with the updated data.
class ViewPlans extends StatefulWidget {
static const id = 'ViewPlans';
#override
_ViewPlansState createState() => _ViewPlansState();
}
class _ViewPlansState extends State<ViewPlans> {
PlannerService _plannerService = new PlannerService();
Future<void> _future;
#override
void initState() {
_future = _plannerService.getPlans();
super.initState();
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
body: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [Color(gradStartColor), Color(gradEndColor)])),
child: FutureBuilder(
future: _future,
builder: (context, snapshot) {
var plans = snapshot.data;
if (plans == 'empty' || plans == null) {
return Center(
child: CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
));
}
return RefreshIndicator(
onRefresh: () {
setState(() {
_future = _plannerService.getPlans();
})
return Future.value(true);
},
child: ListView.separated(
physics: BouncingScrollPhysics(),
itemCount: plans.content.length,
padding: EdgeInsets.symmetric(horizontal: 30, vertical: 30),
separatorBuilder: (context, index) => null,
itemBuilder: (context, index) {
return Container(
height: 100,
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 10),
child: Material(
elevation: 2,
shadowColor: Colors.white,
color: Colors.white,
borderRadius: BorderRadius.circular(10),
child: Center(
child: ListTile(
title: Text(
plans.content[index].task[0].toUpperCase() +
plans.content[index].task.substring(1),
style: TextStyle(
fontSize: 22,
fontFamily: agne,
color: Color(gradStartColor)),
),
trailing: IconButton(
icon: Icon(
Icons.delete,
color: Color(gradStartColor),
),
onPressed: () {
_plannerService
.deletePlan(plans.content[index].id)
.then((value) => handleDelete(value));
},
splashRadius: 25,
),
),
),
),
),
);
},
),
);
},
),
),
),
);
}
Related
I can't figure out how i can aligne an textview left from my action button. Can anyone help with this problem i have? I want it to be like this:
and maybe also how i can show my icon in the action button.
This is the code for my todo list(i have removed some none important code for this question):
class todolist extends StatefulWidget {
#override
_todolistState createState() => _todolistState();
}
class _todolistState extends State<todolist> {
String todoTitle = "";
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
elevation: 0,
title: Text("To-do lijst"),
),
floatingActionButton: FloatingActionButton(
backgroundColor: hexStringToColor("FFBEC8"),
onPressed: () { createTodos();
Navigator.of(context);
Icon(
Icons.add,
color: Colors.white,
);}),
body: Container(
decoration: BoxDecoration(
gradient: LinearGradient(colors: [
hexStringToColor("f1f1f1"),
hexStringToColor("f1f1f1"),
hexStringToColor("f1f1f1"),
hexStringToColor("f1f1f1")
], begin: Alignment.topCenter, end: Alignment.bottomCenter)),
child: StreamBuilder(
stream: stream,
builder: (context, snapshots) {
if (snapshots.hasData) {
return ListView.builder(
itemCount: snapshots.data?.docs.length,
itemBuilder: (context, index) {
DocumentSnapshot documentSnapshot =
snapshots.data!.docs[index];
return Dismissible(
onDismissed: (direction) {
deleteTodos(documentSnapshot["todoTitle"]);
const snackBar = SnackBar(
duration: const Duration(seconds: 2),
content: Text("succesvol verwijderd"),
backgroundColor: AppColor.pinkgood,
);
ScaffoldMessenger.of(context).showSnackBar(snackBar);
},
key: Key(documentSnapshot["todoTitle"]),
child: Card(
elevation: 4,
margin: EdgeInsets.all(8),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8)),
child: ListTile(
title: Text(documentSnapshot["todoTitle"]),
leading:
Icon(
Icons.check_box,
color: hexStringToColor("FFBEC8")
),
trailing: IconButton(
icon: Icon(
Icons.delete,
color: Colors.red,
),
onPressed: () {
deleteTodos(documentSnapshot["todoTitle"]);
}),
),
));
});
} else {
return Align(
alignment: FractionalOffset.bottomCenter,
child: CircularProgressIndicator(),
);
}
}),
));
}
}
This is how it looks now you guys can get an idea of how i want to make it look like.
i have a list which contains list of images, i want to show these in my grid view builder if list if not empty other wise i just want to show static + symbol in my grid view builder.
it is my list
var otherPersonOfferList = <Inventory>[].obs;
and this is my grid view builder which i have extracted as a widget and using it in my screen
import 'package:bartermade/models/Get_Inventory.dart';
import 'package:bartermade/models/inventory.dart';
import 'package:bartermade/widgets/inventoryTile.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../controllers/inventoryController.dart';
class OfferGrid extends StatefulWidget {
OfferGrid(
{Key? key,
required this.list,
required this.modelSheetHeading,
required this.gestureState})
: super(key: key);
String modelSheetHeading;
List<Inventory> list;
bool gestureState;
#override
State<OfferGrid> createState() => _OfferGridState();
}
class _OfferGridState extends State<OfferGrid> {
InventoryController inventoryController = Get.find();
bool isDeleting = false;
#override
Widget build(BuildContext context) {
if (widget.gestureState == true
? (inventoryController.otherPersonOfferList == [] ||
inventoryController.otherPersonOfferList.length == 0 ||
inventoryController.otherPersonOfferList.isEmpty)
: (inventoryController.myOfferList == [] ||
inventoryController.myOfferList.length == 0 ||
inventoryController.myOfferList.isEmpty)) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: GestureDetector(
onTap: () {
showModalBottomSheet(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20)),
context: context,
builder: (context) {
return InventoryTile(
modelSheetHeading: widget.modelSheetHeading,
list: widget.gestureState == true
? inventoryController.traderInventoryList
: inventoryController.myInventoryList1,
inventoryController: inventoryController,
gestureState: widget.gestureState);
});
},
child: Container(
height: 90,
width: 90,
decoration: BoxDecoration(border: Border.all(color: Colors.black)),
child: Icon(
Icons.add,
size: 35,
),
),
),
);
} else {
return GridView.builder(
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemCount: widget.list.length + 1,
gridDelegate:
SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3),
itemBuilder: (context, index) {
if (index == widget.list.length) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: GestureDetector(
onTap: () {
showModalBottomSheet(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20)),
context: context,
builder: (context) {
return InventoryTile(
modelSheetHeading: widget.modelSheetHeading,
list: widget.gestureState == true
? inventoryController.traderInventoryList
: inventoryController.myInventoryList1,
inventoryController: inventoryController,
gestureState: widget.gestureState);
});
},
child: Container(
height: 30,
width: 30,
decoration:
BoxDecoration(border: Border.all(color: Colors.black)),
child: Icon(
Icons.add,
size: 35,
),
),
),
);
} else {
return Stack(
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: GestureDetector(
onLongPress: () {
setState(() {
isDeleting = true;
});
},
onTap : (){
setState(() {
isDeleting = false;
});
},
child: CachedNetworkImage(
fit: BoxFit.cover,
height: 100,
width: 200,
imageUrl: // "https://asia-exstatic-vivofs.vivo.com/PSee2l50xoirPK7y/1642733614422/0ae79529ef33f2b3eb7602f89c1472b3.jpg"
"${widget.list[index].url}",
placeholder: (context, url) => Center(
child: CircularProgressIndicator(
color: Colors.grey,
),
),
errorWidget: (context, url, error) => Icon(Icons.error),
),
),
),
isDeleting == true
? Positioned(
right: 0,
top: 0,
child: CircleAvatar(
backgroundColor: Colors.red,
radius: 10,
child: Icon(
Icons.remove,
size: 14,
),
),
)
: SizedBox()
],
);
}
});
}
}
}
and this is my screen where is just want to check if my list is non empty then fill my grid view with images otherwise just show + sign in grid view
import 'package:bartermade/controllers/inventoryController.dart';
import 'package:bartermade/models/Get_Inventory.dart';
import 'package:bartermade/models/inventory.dart';
import 'package:bartermade/screens/chat/chatScreen.dart';
import 'package:bartermade/utils/app_colors.dart';
import 'package:bartermade/widgets/snackBar.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:get/get.dart';
import '../../../../services/inventoryService.dart';
import '../../../../widgets/offerGrid.dart';
class OfferTradeScreen extends StatefulWidget {
String postUserId;
String postUserName;
OfferTradeScreen({
Key? key,
required this.postUserId,
required this.postUserName,
}) : super(key: key);
#override
State<OfferTradeScreen> createState() => _OfferTradeScreenState();
}
class _OfferTradeScreenState extends State<OfferTradeScreen> {
// TradeController tradeController = Get.put(TradeController());
InventoryController inventoryController = Get.put(InventoryController());
// GiftStorageService giftStorageService = GiftStorageService();
// GiftController giftController = Get.put(GiftController());
// ProfileController profileController = Get.put(ProfileController());
// TradeStorageService tradeStorageService = TradeStorageService();
// TradingService tradingService = TradingService();
// PreferenceService preferenceService = PreferenceService();
late List<Inventory> otherPersonList;
late List<Inventory> myList;
#override
void initState() {
super.initState();
inventoryController.getOtherUserInventory(widget.postUserId);
otherPersonList = inventoryController.otherPersonOfferList;
myList = inventoryController.myOfferList;
}
otherPersonlistener() {
inventoryController.otherPersonOfferList.listen((p0) {
if (this.mounted) {
setState(() {
otherPersonList = p0;
});
}
});
}
mylistener() {
inventoryController.myOfferList.listen((p0) {
if (this.mounted) {
setState(() {
myList = p0;
});
}
});
}
int draggableIndex = 0;
#override
Widget build(BuildContext context) {
print("-------building------");
otherPersonlistener();
mylistener();
SystemChrome.setPreferredOrientations([
DeviceOrientation.portraitUp,
DeviceOrientation.portraitDown,
]);
return Scaffold(
appBar: AppBar(
titleSpacing: 0,
leading: GestureDetector(
onTap: () {
inventoryController.myOfferList.clear();
inventoryController.otherPersonOfferList.clear();
Get.back();
},
child: Icon(Icons.arrow_back)),
title: Text(
"Offer",
style: TextStyle(color: Colors.white),
),
actions: [
Padding(
padding: const EdgeInsets.only(right: 15),
child: GestureDetector(
onTap: () {
Get.to(() => ChatScreen(
currentUserId: widget.postUserId,
recieverId: inventoryController.userId.toString()));
},
child: Icon(Icons.message)),
)
],
),
body: Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
flex: 10,
child: Container(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.only(top: 20),
child: Text(
"${widget.postUserName} Inventory",
style: TextStyle(
color: Colors.black,
fontSize: 20,
fontWeight: FontWeight.w400),
),
),
OfferGrid(
gestureState: true,
list: otherPersonList,
modelSheetHeading: "${widget.postUserName}",
)
],
),
),
),
Expanded(
flex: 1,
child: Divider(
thickness: 2,
color: Colors.black,
height: 3,
),
),
Expanded(
flex: 10,
child: Container(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"My Inventory",
style: TextStyle(
color: Colors.black,
fontSize: 20,
fontWeight: FontWeight.w400),
),
OfferGrid(
gestureState: false,
list: myList,
modelSheetHeading: "My Inventory",
)
],
),
),
),
Center(child: Obx(() {
return inventoryController.makingOffer.value == true
? CircularProgressIndicator(
color: AppColors.pinkAppBar,
)
: ElevatedButton(
onPressed: () {
if (inventoryController.otherPersonOfferList.isEmpty &&
inventoryController.myOfferList.isEmpty) {
showSnackBar(
"Please add both inventories to make offer",
context);
} else {
inventoryController.postOffer(
widget.postUserId, context);
}
},
style: ElevatedButton.styleFrom(
padding: EdgeInsets.symmetric(
vertical: 10, horizontal: 50)),
child: Text("Send Offer"));
}))
],
),
),
);
}
#override
void dispose() {
SystemChrome.setPreferredOrientations([
DeviceOrientation.landscapeRight,
DeviceOrientation.landscapeLeft,
DeviceOrientation.portraitUp,
DeviceOrientation.portraitDown,
]);
super.dispose();
}
}
User following code :
GridView.builder(
shrinkWrap: true,
itemCount: data.length,
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
crossAxisSpacing: 17,
mainAxisSpacing: 17,
),
itemBuilder: (
context,
index,
) {
return Obx(
() => InkWell(
onTap: () {
},
child: ClipRRect(
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
border: Border.all(
color: ColorConfig.colorDarkBlue, width: 2),
image: DecorationImage(
image: AssetImage(ImagePath.unselectedContainer), ///imageURL
fit: BoxFit.fill,
)),
child: Center(
child: Text(data[index].heading,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: ThemeConstants.textThemeFontSize18,
fontWeight: FontWeight.w700,
color: ColorConfig.colorDarkBlue)),
),
height: 150,
),
),
),
);
},
),
currently I am fetching the videos from YouTube. The code below just lists out my videos vertically. I want the videos to be listed horizontally. I tried different approaches, but could not succeed. I tried a bunch of options, but was only left with a number of errors. So I am in need of help on what change should I implement.
class HomeScreen extends StatefulWidget {
#override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
Channel _channel;
bool _isLoading = false;
#override
void initState() {
super.initState();
_initChannel();
SystemChrome.setPreferredOrientations([
DeviceOrientation.portraitUp,
DeviceOrientation.portraitDown,
]);
}
//UCtfK5Khr3psKzUP9HuYYgrw
_initChannel() async {
Channel channel = await APIService.instance
.fetchChannel(channelId: 'Random#');
setState(() {
_channel = channel;
});
}
_buildProfileInfo() {
return Container(
margin: EdgeInsets.all(20.0),
padding: EdgeInsets.all(20.0),
height: 100.0,
decoration: BoxDecoration(
color: Colors.white,
boxShadow: [
BoxShadow(
color: Colors.black12,
offset: Offset(0, 1),
blurRadius: 6.0,
),
],
),
child: Row(
children: <Widget>[
CircleAvatar(
backgroundColor: Colors.white,
radius: 30.0,
backgroundImage: NetworkImage(_channel.profilePictureUrl),
),
SizedBox(width: 12.0),
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
_channel.title,
style: TextStyle(
color: Colors.black,
fontSize: 20.0,
fontWeight: FontWeight.w600,
),
overflow: TextOverflow.ellipsis,
),
// Text(
// '${_channel.subscriberCount} subscribers',
// style: TextStyle(
// color: Colors.grey[600],
// fontSize: 16.0,
// fontWeight: FontWeight.w600,
// ),
// overflow: TextOverflow.ellipsis,
// ),
],
),
)
],
),
);
}
_buildVideo(Video video) {
return GestureDetector(
onTap: () => Navigator.push(
context,
MaterialPageRoute(
builder: (_) => VideoScreen(id: video.id),
),
),
child:
Container(
margin: EdgeInsets.symmetric(horizontal: 20.0, vertical: 5.0),
padding: EdgeInsets.all(10.0),
height: 140.0,
decoration: BoxDecoration(
color: Colors.white,
boxShadow: [
BoxShadow(
color: Colors.black12,
offset: Offset(0, 1),
blurRadius: 6.0,
),
],
),
child: Row(
children: <Widget>[
Image(
width: 150.0,
image: NetworkImage(video.thumbnailUrl),
),
SizedBox(width: 10.0),
Expanded(
child: Text(
video.title,
style: TextStyle(
color: Colors.black,
fontSize: 18.0,
),
),
),
],
),
),
);
}
_loadMoreVideos() async {
_isLoading = true;
List<Video> moreVideos = await APIService.instance
.fetchVideosFromPlaylist(playlistId: _channel.uploadPlaylistId);
List<Video> allVideos = _channel.videos..addAll(moreVideos);
setState(() {
_channel.videos = allVideos;
});
_isLoading = false;
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
toolbarHeight: 30,
title: Text('Jiwdo Pani Audio Video'),
backgroundColor: Colors.indigo[900],
automaticallyImplyLeading: false,
leading: IconButton(
icon: Icon(
Icons.arrow_back,
color: Colors.black,
size: 20,
),
onPressed: () => Get.to(UserMenu()))),
body: _channel != null
? NotificationListener<ScrollNotification>(
onNotification: (ScrollNotification scrollDetails) {
if (!_isLoading &&
_channel.videos.length != int.parse(_channel.videoCount) &&
scrollDetails.metrics.pixels ==
scrollDetails.metrics.maxScrollExtent) {
_loadMoreVideos();
}
return false;
},
child: ListView.builder(
shrinkWrap: true,
//scrollDirection: Axis.horizontal,
itemCount: 1 + _channel.videos.length,
itemBuilder: (BuildContext context, int index) {
if (index == 0) {
return _buildProfileInfo();
}
Video video = _channel.videos[index - 1];
return _buildVideo(video);
},
),
)
: Center(
child: CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation<Color>(
Theme.of(context).primaryColor, // Red
),
),
),
);
}
}
Wrap your listView builder into the container and try to add width and height.
Container(
width : 500,
height : 500,
child:ListView.builder(
shrinkWrap: true,
scrollDirection: Axis.horizontal,
itemCount: 1 + _channel.videos.length,
itemBuilder: (BuildContext context, int index) {
if (index == 0) {
return _buildProfileInfo();
}
Video video = _channel.videos[index - 1];
return _buildVideo(video);
},
),
),
I have the following problem: I have the main class which shows me an image, which calls the drawer. So far it works fine, but when I call another class in the body (CardApplications), the drawer doesn't work. I still don't know how to fix it.
When i don't call class
"CrearAplicaciones" the drawer is called correctly with _scaffoldKey, but if I call it absolutely nothing happens, not even an error.
class HomeUsuario extends StatefulWidget {
final StorageClass storage;
HomeUsuario({Key key, #required this.storage}) : super(key: key);
#override
HomeUsuarioState createState() => HomeUsuarioState();
}
class HomeUsuarioState extends State<HomeUsuario> {
final prefs = PreferenciasUsuario();
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
Application app;
#override
// guardo en el documento global la información del usuario.
void initState() {
widget.storage.writeData(
'["${prefs.codigo}","${prefs.ciudad}","${prefs.oficina}","${prefs.seccion}"]');
widget.storage.readData();
super.initState();
}
#override
Widget build(BuildContext context) {
final size = MediaQuery.of(context).size;
return Scaffold(
key: _scaffoldKey,
drawer: MenuWidget(),
body: Stack(
children: [
Padding(
padding: EdgeInsets.only(left: size.width*0.7,top: size.height*0.1),
child: InkWell(
onTap: () => _scaffoldKey.currentState.openDrawer(),
child: Container(
child: ClipRRect(
borderRadius: BorderRadius.circular(20.0),
child: Image.asset('assets/images/USUARIO_ICONO.png',
width: 110.0,
height: 110.0
),
),
),
),
),
Container(
color: Colors.white70,
padding: EdgeInsets.only(
left: size.height * 0.02,
right: size.height * 0.02,
top: size.height * 0.1),
child: SingleChildScrollView(
child: Column(children: <Widget>[
ListTile(
leading: Icon(
FontAwesomeIcons.thLarge,
color: Colors.red,
),
title: Text(
'Mis aplicaciones',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Colors.red),
),
),
]))),
CardAPlicaciones(),
],
),
);
}
}
class CardAPlicaciones extends StatelessWidget {
#override
Widget build(BuildContext context) {
final servicesProvider = ServiciosLogin();
return Container(
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage("assets/images/FONDO.png"),
fit: BoxFit.cover,
)),
child: Padding(
padding: const EdgeInsets.fromLTRB(40, 100, 0, 0),
child: FutureBuilder(
future: servicesProvider.listarApps(),
builder: (BuildContext context,
AsyncSnapshot<List<Aplicacion>> snapshot) {
if (snapshot.hasData) {
final aplicaciones = snapshot.data;
return GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2),
itemCount: aplicaciones.length,
itemBuilder: (BuildContext context, int index) {
return new _Card(aplicaciones[index]);
});
} else {
return Center(child: CircularProgressIndicator());
}
}),
),
);
}
// obtiene la informacion de la app instalada
}
class _Card extends StatelessWidget {
final Aplicacion app;
_Card(this.app);
#override
Widget build(BuildContext context) {
return Container(
child: Stack(
children: <Widget>[
Row(
children: <Widget>[
SizedBox(width: 10.0),
],
),
Positioned(
top: 35,
left: 0,
child: Column(
children: <Widget>[
new RawMaterialButton(
onPressed: () => {DeviceApps.openApp(app.paquete)},
child: CircleAvatar(
backgroundColor: Colors.white,
radius: 50,
backgroundImage: NetworkImage(global.url + app.icono),
)),
Text(app.nombre,
style: TextStyle(
color: Colors.black,
fontSize: 16,
fontWeight: FontWeight.bold)),
],
))
],
),
);
}
}
Try this, put the InkWell inside a Builder to get the context of the scaffold.
child: Builder(scaffoldContext){
return InkWell(
onTap: () => Scaffold.of(scaffoldContext).currentState.openDrawer(),
child: Container(
child: ClipRRect(
borderRadius: BorderRadius.circular(20.0),
child: Image.asset('assets/images/USUARIO_ICONO.png',
width: 110.0,
height: 110.0
),
),
),
),
}
Currently I'm using flutter package 'Reorderables' to show a reorderable listview which contains several images.These images can be deleted from listview through a button , everything works fine. But the listview rebuild everytime when I delete an image. I'm using a class called 'ReorderableListviewManager' with ChangeNotifier to update images and Provider.of<ReorderableListviewManager>(context) to get latest images . The problem now is that using Provider.of<ReorderableListviewManager>(context) makes build() called everytime I delete an image , so the listview rebuid. I koow I
can use consumer to only rebuild part of widget tree, but it seems like that there's no place to put consumer in children of this Listview. Is there a way to rebuild only image but not whole ReorderableListview ? Thanks very much!
Below is my code:
class NotePicturesEditScreen extends StatefulWidget {
final List<Page> notePictures;
final NotePicturesEditBloc bloc;
NotePicturesEditScreen({#required this.notePictures, #required this.bloc});
static Widget create(BuildContext context, List<Page> notePictures) {
return Provider<NotePicturesEditBloc>(
create: (context) => NotePicturesEditBloc(),
child: Consumer<NotePicturesEditBloc>(
builder: (context, bloc, _) =>
ChangeNotifierProvider<ReorderableListviewManager>(
create: (context) => ReorderableListviewManager(),
child: NotePicturesEditScreen(
bloc: bloc,
notePictures: notePictures,
),
)),
dispose: (context, bloc) => bloc.dispose(),
);
}
#override
_NotePicturesEditScreenState createState() => _NotePicturesEditScreenState();
}
class _NotePicturesEditScreenState extends State<NotePicturesEditScreen> {
PreloadPageController _pageController;
ScrollController _reorderableScrollController;
List<Page> notePicturesCopy;
int longPressIndex;
List<double> smallImagesWidth;
double scrollOffset = 0;
_reorderableScrollListener() {
scrollOffset = _reorderableScrollController.offset;
}
#override
void initState() {
Provider.of<ReorderableListviewManager>(context, listen: false)
.notePictures = widget.notePictures;
notePicturesCopy = widget.notePictures;
_reorderableScrollController = ScrollController();
_pageController = PreloadPageController();
_reorderableScrollController.addListener(_reorderableScrollListener);
Provider.of<ReorderableListviewManager>(context, listen: false)
.getSmallImagesWidth(notePicturesCopy, context)
.then((imagesWidth) {
smallImagesWidth = imagesWidth;
});
super.initState();
}
#override
void dispose() {
_pageController.dispose();
_reorderableScrollController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
ReorderableListviewManager reorderableManager =
Provider.of<ReorderableListviewManager>(context, listen: false);
return SafeArea(
child: Scaffold(
appBar: AppBar(
backgroundColor: Colors.white,
shape: Border(bottom: BorderSide(color: Colors.black12)),
iconTheme: IconThemeData(color: Colors.black87),
elevation: 0,
automaticallyImplyLeading: false,
titleSpacing: 0,
centerTitle: true,
title: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Container(
child: IconButton(
padding: EdgeInsets.only(left: 20, right: 12),
onPressed: () => Navigator.of(context).pop(),
icon: Icon(Icons.close),
),
),
Text('編輯',
style: TextStyle(color: Colors.black87, fontSize: 18))
],
),
actions: <Widget>[
FlatButton(
onPressed: () {},
child: Text(
'下一步',
),
)
],
),
backgroundColor: Color(0xffeeeeee),
body: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Spacer(),
StreamBuilder<List<Page>>(
initialData: widget.notePictures,
stream: widget.bloc.notePicturesStream,
builder: (context, snapshot) {
notePicturesCopy = snapshot.data;
return Container(
margin: EdgeInsets.symmetric(horizontal: 20),
height: MediaQuery.of(context).size.height * 0.65,
child: PreloadPageView.builder(
preloadPagesCount: snapshot.data.length,
controller: _pageController,
itemCount: snapshot.data.length,
onPageChanged: (index) {
reorderableManager.updateCurrentIndex(index);
reorderableManager.scrollToCenter(
smallImagesWidth,
index,
scrollOffset,
_reorderableScrollController,
context);
},
itemBuilder: (context, index) {
return Container(
child: Image.memory(
File.fromUri(
snapshot.data[index].polygon.isNotEmpty
? snapshot.data[index]
.documentPreviewImageFileUri
: snapshot.data[index]
.originalPreviewImageFileUri)
.readAsBytesSync(),
gaplessPlayback: true,
alignment: Alignment.center,
),
);
}),
);
},
),
Spacer(),
Container(
height: MediaQuery.of(context).size.height * 0.1,
margin: EdgeInsets.symmetric(horizontal: 20),
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: ReorderableRow(
scrollController: _reorderableScrollController,
buildDraggableFeedback: (context, constraints, __) =>
Container(
width: constraints.maxWidth,
height: constraints.maxHeight,
child: Image.memory(File.fromUri(
notePicturesCopy[longPressIndex]
.polygon
.isNotEmpty
? notePicturesCopy[longPressIndex]
.documentPreviewImageFileUri
: notePicturesCopy[longPressIndex]
.originalPreviewImageFileUri)
.readAsBytesSync()),
),
onReorder: (oldIndex, newIndex) async {
List<Page> result = await widget.bloc.reorderPictures(
oldIndex,
newIndex,
reorderableManager.notePictures);
_pageController.jumpToPage(newIndex);
reorderableManager.updateNotePictures(result);
reorderableManager
.getSmallImagesWidth(result, context)
.then((imagesWidth) {
smallImagesWidth = imagesWidth;
});
},
footer: Container(
width: 32,
height: 32,
margin: EdgeInsets.only(left: 16),
child: SizedBox(
child: FloatingActionButton(
backgroundColor: Colors.white,
elevation: 1,
disabledElevation: 0,
highlightElevation: 1,
child: Icon(Icons.add, color: Colors.blueAccent),
onPressed: notePicturesCopy.length >= 20
? () {
Scaffold.of(context)
.showSnackBar(SnackBar(
content: Text('筆記上限為20頁 !'),
));
}
: () async {
List<Page> notePictures =
await widget.bloc.addPicture(
reorderableManager.notePictures);
List<double> imagesWidth =
await reorderableManager
.getSmallImagesWidth(
notePictures, context);
smallImagesWidth = imagesWidth;
reorderableManager.updateCurrentIndex(
notePictures.length - 1);
reorderableManager
.updateNotePictures(notePictures);
_pageController
.jumpToPage(notePictures.length - 1);
},
),
),
),
children: Provider.of<ReorderableListviewManager>(
context)
.notePictures
.asMap()
.map((index, page) {
return MapEntry(
index,
Consumer<ReorderableListviewManager>(
key: ValueKey('value$index'),
builder: (context, manager, _) =>
GestureDetector(
onTapDown: (_) {
longPressIndex = index;
},
onTap: () {
reorderableManager.scrollToCenter(
smallImagesWidth,
index,
scrollOffset,
_reorderableScrollController,
context);
_pageController.jumpToPage(index);
},
child: Container(
margin: EdgeInsets.only(
left: index == 0 ? 0 : 12),
decoration: BoxDecoration(
border: Border.all(
width: 1.5,
color: index ==
manager
.getCurrentIndex
? Colors.blueAccent
: Colors.transparent)),
child: index + 1 <=
manager.notePictures.length
? Image.memory(
File.fromUri(manager
.notePictures[
index]
.polygon
.isNotEmpty
? manager
.notePictures[
index]
.documentPreviewImageFileUri
: manager
.notePictures[
index]
.originalPreviewImageFileUri)
.readAsBytesSync(),
gaplessPlayback: true,
)
: null),
),
));
})
.values
.toList()),
)),
Spacer(),
Container(
decoration: BoxDecoration(
color: Colors.white,
border: Border(top: BorderSide(color: Colors.black12))),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
FlatButton(
onPressed: () async => await widget.bloc
.cropNotePicture(reorderableManager.notePictures,
_pageController.page.round())
.then((notePictures) {
reorderableManager.updateNotePictures(notePictures);
reorderableManager
.getSmallImagesWidth(notePictures, context)
.then((imagesWidth) {
smallImagesWidth = imagesWidth;
});
}),
child: Column(
children: <Widget>[
Icon(
Icons.crop,
color: Colors.blueAccent,
),
Container(
margin: EdgeInsets.only(top: 1),
child: Text(
'裁切',
style: TextStyle(color: Colors.blueAccent),
),
)
],
),
),
FlatButton(
onPressed: () {
int deleteIndex = _pageController.page.round();
widget.bloc
.deletePicture(
reorderableManager.notePictures, deleteIndex)
.then((notePictures) {
if (deleteIndex == notePictures.length) {
reorderableManager
.updateCurrentIndex(notePictures.length - 1);
}
reorderableManager.updateNotePictures(notePictures);
reorderableManager
.getSmallImagesWidth(notePictures, context)
.then((imagesWidth) {
smallImagesWidth = imagesWidth;
});
if (reorderableManager.notePictures.length == 0) {
Navigator.pop(context);
}
});
},
child: Column(
children: <Widget>[
Icon(
Icons.delete_outline,
color: Colors.blueAccent,
),
Container(
margin: EdgeInsets.only(top: 1),
child: Text(
'刪除',
style: TextStyle(color: Colors.blueAccent),
),
),
],
),
)
],
),
)
],
)),
);
}
}
You can't prevent a rebuild on your ReorderableListView widget because it will be rebuild every time there's an update on the Provider. What you can do here is to keep track the current index of all visible ListView items. When new data should be displayed coming from the Provider, you can retain the current indices of previous ListView items, and add the newly added items at the end of the list, or wherever you like.