Flutter List view builder doesn't shrink when Keyboard appears - flutter

I'm creating a chat feature in flutter but noticed this behavior on IOS that doesnt shrink the list so you can see the last sent message. How can I have the listview builder shrink to show the last message when the keyboard appears?
Note: This issue doesn't happen on Android
Scaffold(
resizeToAvoidBottomInset: true,
body: Stack(
children: <Widget>[
StreamBuilder(
stream: _chats,
builder: (context, snapshot) {
if (!snapshot.hasData) return Container();
return snapshot.hasData
? GestureDetector(
onPanDown: (_) {
FocusScope.of(context).requestFocus(FocusNode());
},
child: ListView.builder(
shrinkWrap: true,
controller: _scrollController,
padding: EdgeInsets.only(top: 10, bottom: 100),
itemCount: snapshot.data.docs.length,
itemBuilder: (context, index) {
return MessageWidget(
tripId: widget.docId,
uid: snapshot.data.docs[index].data()["uid"],
messageId: snapshot.data.docs[index].id,
message: snapshot.data.docs[index].data()["message"],
sender: snapshot.data.docs[index].data()["senderName"],
sentByMe: widget.uid ==
snapshot.data.docs[index].data()["uid"],
mediaFileUrl:
snapshot.data.docs[index].data()["mediaFileUrl"],
);
}),
)
: Container();
},
);
]
)
)

I think you can try the 'reverse' property from the ListView.builder.
Tell me if this example didn't fit your needs, can you share us your code ? (I didn't see why you use a Stack and what could be the issue around that).
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: <Widget>[
Expanded(
child: Stack(
children: <Widget>[
StreamBuilder<dynamic>(
builder: (context, dynamic snapshot) {
return GestureDetector(
onPanDown: (_) {
FocusScope.of(context).unfocus();
},
child: ListView.builder(
reverse: true,
shrinkWrap: true,
itemCount: 100,
padding: const EdgeInsets.only(top: 10, bottom: 10),
itemBuilder: (context, index) {
return ListTile(title: Text(index.toString()));
},
),
);
},
),
],
),
),
Container(
padding: const EdgeInsets.all(8),
color: Colors.black12,
child: const TextField(),
),
],
),
);
}
}

Related

Flutter Grid View Reorder & Drag Drop onto Another Item and Merge

I have been trying to add drag/drop support to my app, currently what I have come with is using this library:
reorderable_grid_view
I used this example code:
code link
The reason I used this library is that it's smooth enough of animations when dragging. But what I want to do is to drag one item to another so that I can merge the one to another object when I drop. (It's like in Android/iOS home screen where you can drag apps to folders or drag into another that it creates a folder)
I have searched all the site but couldn't come across with such thing, only drag/drop libraries are available. Can anyone help me on this?
Thanks in advance.
class MyHomePage extends StatelessWidget {
MyHomePage({super.key});
final ValueNotifier<List<ValueNotifier<List<Widget>>>> items = ValueNotifier([
ValueNotifier([Text("A")]),
ValueNotifier([Text("B")]),
ValueNotifier([Text("C")]),
ValueNotifier([Text("D")]),
]);
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Expanded(
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 20.0),
child: ValueListenableBuilder(
valueListenable: items,
builder: (BuildContext context, List<ValueNotifier<List<Widget>>> folders, Widget? child) {
return GridView.builder(
physics: const NeverScrollableScrollPhysics(),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3),
itemCount: folders.length,
itemBuilder: (context, index) {
ValueNotifier<List<Widget>> item = folders[index];
return LongPressDraggable(
delay: const Duration(milliseconds: 500),
feedback: SizedBox(width: MediaQuery.of(context).size.width / 4, height: MediaQuery.of(context).size.width / 4, child: FittedBox(child: Icon(Icons.folder))),
data: index,
childWhenDragging: const SizedBox(),
child: DragTarget(
onAccept: (data) {
List<Widget> alreadyHaved = item.value;
alreadyHaved.addAll(folders[data as int].value);
item.value = alreadyHaved;
items.value.removeAt(data);
items.notifyListeners();
},
builder: (context, candidateData, rejectedData) {
return ValueListenableBuilder(
valueListenable: item,
builder: (BuildContext context, List<Widget> boxValues, Widget? child) {
return Stack(children: [
const Positioned.fill(
child: FittedBox(
child: Icon(
Icons.folder,
color: Colors.amber,
))),
Positioned.fill(
child: LayoutBuilder(
builder: (p0, p1) => SizedBox(
height: p1.maxHeight * .7,
width: p1.maxWidth * .7,
child: Center(
child: Wrap(
children: boxValues,
),
))),
)
]);
},
);
},
),
);
});
},
),
),
),
],
),
);
}
}

What is wrong, I've used the relevant GetX widget and it still throws this error

This is the code where the error occurred using GetX. If anyone want to help thanks for advance
body: SafeArea(
child: Column(
children: [
Expanded(
child: GetX<CustomerController> (
builder: (customerController) {
return ListView.builder(
itemCount: customerController.count,
itemBuilder: (context, index) {
return ListTile(
selectedTileColor:Colors.blueGrey.shade50,
title: Text(customerController.results.string),
);
});
},
),
)
],
),
),
Create a GetX Controller and then assign it to your GetX Widget
GetX<ControllerName>(
builder: (customerController) {
return ListView.builder(
itemCount: customerController.controllervalueinobx.length, //should be obx/Rx type variable
itemBuilder: (context, index) {
return ListTile(
selectedTileColor: Colors.blueGrey.shade50,
title: Text(
"customerController.results.string",
), );
});
},
),

multi type list View in Flutter

How I can display list View with multi-type for example(item 1: text, item 2: image with Text ...)
using flutter?
Here is the code:
I need to make the ListView show onlyText in item1, imageWithText for item2 and so on, How I can do that?
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Column(
children: [
SizedBox(height: 5),
ListView.separated(
shrinkWrap: true,
itemBuilder: (context, index) => onlyText(),
separatorBuilder: (context, index) => SizedBox(height: 10),
itemCount: 100,
),
],
),
);
}
}
Widget imageWithText() => Container(
child: Card(
child: Row(
children: [
Text(
'sara ahmad',
style: TextStyle(fontSize: 16),
),
SizedBox(width: 10),
Image.network(
'https://th.bing.com/th/id/R.e3a5da5209f4c39f1899456c94371a6f?rik=mz9sVBWxRJKGgA&riu=http%3a%2f%2fmedia1.santabanta.com%2ffull1%2fAnimals%2fHorses%2fhorses-62a.jpg&ehk=o%2fS9l8DSJtUbl%2bYcrwLMJy6W4MfUby7bTUHRwJu7a%2bU%3d&risl=&pid=ImgRaw&r=0',
width: 100,
height: 100,
),
],
),
),
);
Widget onlyText() => Container(
child: Card(
child: Row(
children: [
Text(
'sara ahmad',
style: TextStyle(fontSize: 16),
),
SizedBox(width: 10),
Text('Nour'),
],
),
),
);
In the itemBuilder you can check if the item is only text or image with text with a ternary operator ?:, i.e. condition ? expr1 : expr2, like so:
itemBuilder: (context, index) => index == 0 ? onlyText() : imageWithText(),
Or, if you have a list of more than 2 items it could be something like this (assuming the items have a property bool isOnlyText):
itemBuilder: (context, index) => _chats[index].isOnlyText
? onlyText()
: imageWithText(),
Below is the result of the 1st snippet above:
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Column(
children: [
SizedBox(height: 5),
Expanded(
child: ListView.separated(
shrinkWrap: true,
itemBuilder: (context, index) =>
index == 0 ? onlyText() : imageWithText(),
separatorBuilder: (context, index) => SizedBox(height: 10),
itemCount: 100,
),
),
],
),
);
}
///crete an empty widget list
List<Widget> item_list=[];
///create a function to add data to list and call this function in the initstate
add_items_to_list()async{
item_list.add(Text(('data')));
item_list.add(Image.asset('name'));
///add as much as you want
}
///use widget as below
Widget items()=>ListView(
children: item_list,
);
if you want to show static list, you can do like this
itemBuilder: (context, index) => index.isEven
? onlyText()
: imageWithText(),
or you have dynamic data then follow below
let's assume you have list of Model class like this
class Model{
String text,
String img,
}
var list = <Model>[]; //your data will be here
and to check if is there only image, you need condition like below,
so in your ListView you can check like this
itemBuilder: (context, index) => list[index].img == null
? onlyText()
: imageWithText(),

Flutter - resizeToAvoidBottomInset property makes my ListView covered by keyboard

I will describe my problem from the very first time. I have a page with BottomNavigationBar, ListView, and a Custom Search Widget (using TextField inside it). Whenever I use the Search Widget the keyboard appears and bringing unnecessary white box on it (I have browsed this problem a lot and found this fix by using resizeToAvoidBottomInset: false as my Scaffold property. Using that property fixes the white box problem, but it gives a new problem: bottom-half of my ListView is now blocked by the keyboard because the ListView height is not getting resized when the keyboard appears.
Here is my view code:
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
FocusManager.instance.primaryFocus?.unfocus();
},
child: SafeArea(
child: Scaffold(
resizeToAvoidBottomInset: false,
body: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Container(
width: double.infinity,
margin: EdgeInsets.all(16),
child: const Text("Inventory",
textAlign: TextAlign.center,
style: TextStyle(
fontFamily: "Quicksand",
fontSize: 20,
fontWeight: FontWeight.w700)),
),
Container(
child: _buildSearch(),
),
Flexible(
child: Container(
child: FutureBuilder(
future: _fetchData(),
builder: (context, AsyncSnapshot snapshot) {
if (isFiltered) {
isFiltered = false;
return ListView.builder(
// itemCount: snapshot.data.length,
physics: BouncingScrollPhysics(),
itemCount: arrFilteredStock.length,
itemBuilder: (context, index) {
var id = arrFilteredStock[index].id;
var code = arrFilteredStock[index].itemCode;
var comname = arrFilteredStock[index].itemComname;
var unit = arrFilteredStock[index].itemUnit;
var qty =
arrFilteredStock[index].itemStockBalanceQty;
return StockCard(
stock: Stock(id, code, comname, unit, qty));
},
);
} else {
if (snapshot.data == null) {
return Container(
child: const Center(
child: Text("Loading..."),
));
} else {
return ListView.builder(
physics: BouncingScrollPhysics(),
itemCount: snapshot.data.length,
itemBuilder: (context, index) {
var id = snapshot.data[index].id;
var code = snapshot.data[index].itemCode;
var comname = snapshot.data[index].itemComname;
var unit = snapshot.data[index].itemUnit;
var qty =
snapshot.data[index].itemStockBalanceQty;
return StockCard(
stock: Stock(id, code, comname, unit, qty));
},
);
}
}
}),
)
)
]))),
);
}
I have found a temporary solution:
White necessary white box is gone, ListView can be scrolled until the last data in it. Only problem is after scrolled until the final data of ListView, the white box appears again. I think this is better comparing to other solutions.
Here's the revisioned code:
#override
Widget build(BuildContext context) {
final bottom = MediaQuery.of(context).viewInsets.bottom;
return GestureDetector(
onTap: () {
FocusManager.instance.primaryFocus?.unfocus();
},
child: SafeArea(
child: Scaffold(
resizeToAvoidBottomInset: false,
body: Column(mainAxisSize: MainAxisSize.max, children: <Widget>[
Container(
width: double.infinity,
margin: EdgeInsets.all(16),
child: const Text("Inventory",
textAlign: TextAlign.center,
style: TextStyle(
fontFamily: "Quicksand",
fontSize: 20,
fontWeight: FontWeight.w700)),
),
Container(
child: _buildSearch(),
),
Flexible(
child: Container(
child: FutureBuilder(
future: _fetchData(),
builder: (context, AsyncSnapshot snapshot) {
if (isFiltered) {
isFiltered = false;
return ListView.builder(
// itemCount: snapshot.data.length,
padding: EdgeInsets.only(bottom: bottom),
physics: BouncingScrollPhysics(),
itemCount: arrFilteredStock.length,
itemBuilder: (context, index) {
var id = arrFilteredStock[index].id;
var code = arrFilteredStock[index].itemCode;
var comname = arrFilteredStock[index].itemComname;
var unit = arrFilteredStock[index].itemUnit;
var qty =
arrFilteredStock[index].itemStockBalanceQty;
return StockCard(
stock: Stock(id, code, comname, unit, qty));
},
);
} else {
if (snapshot.data == null) {
return Container(
child: const Center(
child: Text("Loading..."),
));
} else {
return ListView.builder(
padding: EdgeInsets.only(bottom: bottom),
physics: BouncingScrollPhysics(),
itemCount: snapshot.data.length,
itemBuilder: (context, index) {
var id = snapshot.data[index].id;
var code = snapshot.data[index].itemCode;
var comname = snapshot.data[index].itemComname;
var unit = snapshot.data[index].itemUnit;
var qty =
snapshot.data[index].itemStockBalanceQty;
return StockCard(
stock: Stock(id, code, comname, unit, qty));
},
);
}
}
}),
)),
]))),
);
}
This is just a temporary solution. Any solution will be appreciated.
Add this padding in your code at the top of your fields when you add data keyboard appears first wrape in container then check other area I resolved same issue adding this
Padding(
padding: EdgeInsets.only(
bottom: MediaQuery.of(context).viewInsets.bottom));

Flutter page jumps to top after setState({})

I display many images in a Staggered Gridview in a Flutter application.
Everytime I call setState({}), for example after deleting an item, the page jumps to top. How could I remove this behavior?
This is my code:
final _scaffoldKey = new GlobalKey<ScaffoldState>();
.. outside the build function. And then...
return loadingScreen == true
? LoadingScreen()
: Scaffold(
key: _scaffoldKey,
body: CustomScrollView(
slivers: <Widget>[
_AppBar(
theme: theme,
index: index,
albumImagePath: albumImagePath,
albumID: albumID,
albumValue: albumValue,
addPictureToGallery: _addPictureToGallery,
),
SliverToBoxAdapter(
child: Column(
children: <Widget>[
InfoBar(
albumPicturesSum: albumPicturesSum,
getBilderString: _getBilderString,
theme: theme,
getVideoProgress: _getVideoProgress,
progress: progress,
),
albumID == 99999999
? // Demo Projekt
DemoImageGrid(
demoImageList: demoImageList,
getDemoImagesJson: _getDemoImagesJson,
)
: UserImageGrid(
picturesData: picturesData,
albumID: albumID,
showPictureActions: _showPictureActions)
],
),
)
],
),
);
}
The UserImageGrid looks like the following:
class UserImageGrid extends StatelessWidget {
final Pictures picturesData;
final int albumID;
final Function showPictureActions;
final _key = new UniqueKey();
UserImageGrid(
{#required this.picturesData,
#required this.albumID,
#required this.showPictureActions});
#override
Widget build(BuildContext context) {
return FutureBuilder(
key: _key,
future: picturesData.getPicturesFromAlbum(albumID),
builder: (BuildContext context, AsyncSnapshot snapshot) {
// Normale Projekte
if (snapshot.hasData && snapshot.data.length == 0) {
return Center(
child: Column(
children: <Widget>[
Lottie.asset('assets/lottie/drone.json',
width: 250,
options: LottieOptions(enableMergePaths: false)),
],
),
);
}
if (!snapshot.hasData ||
snapshot.connectionState == ConnectionState.waiting) {
return Center(
child: CircularProgressIndicator(),
);
} else {
return Container(
child: StaggeredGridView.countBuilder(
physics: NeverScrollableScrollPhysics(),
shrinkWrap: true,
padding: EdgeInsets.all(0),
crossAxisCount: 6,
itemCount: snapshot.data.length,
itemBuilder: (BuildContext context, int index) =>
GestureDetector(
onLongPress: () {
showPictureActions(snapshot.data[index]);
},
onTap: () async {
await showDialog(
context: context,
builder: (_) {
return Dialog(
child: Stack(
children: [
Container(
margin: const EdgeInsets.symmetric(
vertical: 10.0,
horizontal: 10.0,
),
height: 500.0,
child: ClipRect(
child: PhotoView(
maxScale:
PhotoViewComputedScale.covered * 2.0,
minScale:
PhotoViewComputedScale.contained *
0.8,
initialScale:
PhotoViewComputedScale.covered,
imageProvider: FileImage(
File(snapshot.data[index].path))),
),
),
Positioned(
bottom: 20,
left: 20,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
DateFormat(tr("date_format")).format(
snapshot.data[index].timestamp
.toDateTime()),
style: TextStyle(color: Colors.white),
),
),
)
],
));
});
},
child: Container(
child: Image.file(
File(snapshot.data[index].thumbPath),
fit: BoxFit.cover,
)),
),
staggeredTileBuilder: (int index) =>
new StaggeredTile.count(2, index.isEven ? 2 : 2),
mainAxisSpacing: 5.0,
crossAxisSpacing: 5.0,
),
);
}
});
}
}
What could be the issue?
I found a solution for this issue. The problem was not the setState({}). It was the return Widget of the FutureBuilder.
I changed
if (!snapshot.hasData || snapshot.connectionState == ConnectionState.waiting) {
return Center(child: CircularProgressIndicator());
}
to:
if (!snapshot.hasData || snapshot.connectionState == ConnectionState.waiting) {
return Container(
height: MediaQuery.of(context).size.height,
);
}
I donĀ“t exactly know why, but with this change the page is not jumping to top anymore on setState({})