ListTile.tileColor is late to render - flutter-layout

When ListView resizes, the selection is late to catch up.
Here is the code:
Widget build(BuildContext context) {
return ListView.builder(
controller: controller.scrollController,
shrinkWrap: true,
itemCount: controller.snapshots.value.length,
itemBuilder: (context, index) {
return ListTile(
tileColor: controller.selectedIndex.value == index
? Theme.of(context).selectedRowColor
: null,
title: _SnapshotListTitle(
item: controller.snapshots.value[index],
selected: index == controller.selectedIndex.value,
),
onTap: () => controller.selectedIndex.value = index,
);
},
);
}
Code: https://github.com/flutter/devtools/blob/50ab9a87440f8a0b59ceb15cae34600384f5c42b/packages/devtools_app/lib/src/screens/memory/panes/diff/diff_pane.dart#L132
Video: https://github.com/flutter/devtools/pull/4260#issuecomment-1188500440

Replaced ListTile with Container + InkWell to make it working:
itemBuilder: (context, index) {
return Container(
color: controller.selectedIndex.value == index
? Theme.of(context).selectedRowColor
: null,
height: 40,
child: InkWell(
canRequestFocus: false,
onTap: () => controller.selectedIndex.value = index,
child: _SnapshotListTitle(
item: controller.snapshots.value[index],
selected: index == controller.selectedIndex.value,
),
),
);

Related

Flutter; List obs does not refresh new elements on default tab controller tabs

Hello i am learning the state using the getX methods. When the app loads i am adding the categories in the list but the issue is not updating. can anybody look whats my wrong?
Restaurant Ui Button Page:
HomeController con = Get.put(HomeController());
Widget _cardRestoran(BuildContext context, Restaurants restaurant) {
return GestureDetector(
onTap: () {
con.restoranProducts(context, restaurant);
},
child: Column(
children: [
Container(
margin: EdgeInsets.only(top: 15, left: 20, right: 20),
child: ListTile(
title: Text('${restaurant.name ?? ''}' ,),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(height: 5),
Text(
' Şehir: ${restaurant.il ?? ''} - İlçe: ${restaurant.ilce ?? ''}' ,
maxLines: 2,
style: TextStyle(
fontSize: 13
),
),
SizedBox(height: 15),
],
),
trailing: Container(
height: 70,
width: 60,
// padding: EdgeInsets.all(2),
child: ClipRRect(
borderRadius: BorderRadius.circular(12),
child: FadeInImage(
image: restaurant.image != null
? NetworkImage(restaurant.image ?? '')
: AssetImage('assets/img/no-image.png') as ImageProvider,
fit: BoxFit.cover,
fadeInDuration: Duration(milliseconds: 50),
placeholder: AssetImage('assets/img/no-image.png'),
),
),
),
),
),
Divider(height: 1, color: Colors.grey[300], indent: 37, endIndent: 37,)
],
),
);
}
Home Controller
class HomeController extends GetxController{
void restoranProducts(BuildContext context, Restaurants restaurants) async{
await showMaterialModalBottomSheet(
context: context,
useRootNavigator: true,
builder: (context) => RestoranProducts(restaurants: restaurants),
);
}
}
Restaurant Category Controller :
class ClientRestoranProductListController extends GetxController{
String res_id;
List<Category> categories = <Category>[].obs;
ClientRestoranProductListController(#required this.res_id){
getCategories();
}
void getCategories() async {
var result = await categoriesProvider.restaurantCategory("${this.res_id}");
categories.clear();
categories.addAll(result);
}
}
I also tried seperate but it still didnt work.
Restoran Products Category List UI page:
class RestoranProducts extends StatefulWidget {
final Restaurants? restaurants;
const RestoranProducts({Key? key, required this.restaurants}) : super(key: key);
#override
State<RestoranProducts> createState() => _RestoranProductsState();
}
class _RestoranProductsState extends State<RestoranProducts> {
Widget build(BuildContext context) {
String restoranSahibi = "${widget.restaurants?.user_id}";
ClientRestoranProductListController con = Get.put(ClientRestoranProductListController(restoranSahibi));
return Obx(() => DefaultTabController(
length: con.categories.length,
child: Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: () => setState((){
})),
drawer: HomeNavigator(),
appBar: PreferredSize(
preferredSize: Size.fromHeight(110),
child:AppBar(
flexibleSpace: Container(
decoration: const BoxDecoration(
gradient: LinearGradient(
colors:
[
Colors.amber,
Colors.black,
],
begin: FractionalOffset(0.0, 0.0),
end: FractionalOffset(1.0, 0.0),
stops: [0.0, 1.0],
tileMode: TileMode.clamp,
)
),
margin: EdgeInsets.only(top: 15),
alignment: Alignment.topCenter,
child: Wrap(
direction: Axis.horizontal,
children: [
_textFieldSearch(context),
_iconShoppingBag()
],
),
),
centerTitle: true,
iconTheme: IconThemeData(color: Colors.white),
bottom: TabBar(
isScrollable: true,
indicatorColor: Colors.white,
labelColor: Colors.black,
unselectedLabelColor: Colors.white,
indicatorSize: TabBarIndicatorSize.tab,
indicator: BoxDecoration(
borderRadius: BorderRadius.only(topLeft: Radius.circular(20), topRight: Radius.circular(20)),
color: Colors.white
),
tabs: List<Widget>.generate(con.categories.length, (index) {
return Tab(
child: Text(con.categories[index].name ?? ''),
);
}),
),
) ,
),
body: TabBarView(
key: Key(Random().nextDouble().toString()),
children: con.categories.map((Category category) {
return FutureBuilder(
future: con.getProducts(category.id ?? '1', con.productName.value),
builder: (context, AsyncSnapshot<List<Product>> snapshot) {
if (snapshot.hasData) {
if (snapshot.data!.length > 0) {
return ListView.builder(
itemCount: snapshot.data?.length ?? 0,
itemBuilder: (_, index) {
return _cardProduct(context, snapshot.data![index]);
}
);
}
else {
return NoDataWidget(text: 'No category');
}
}
else {
return NoDataWidget(text: 'No category');
}
}
);
}).toList(),
)
),
));
}
}
i tried seperate ways but it did not work.

DraggableScrollableSheet child starts in the middle of the sheet in flutter

I have a listview in the main scaffold, and I added a gesturedetector in the items to display a bottomsheet for item details. The issue is that the content starts in the middle of the sheet.
this is how the code for the sheet:
showModalBottomSheet(
context: context,
builder: (context) => SizedBox.expand(
child: DraggableScrollableSheet(
builder: (BuildContext context, ScrollController scrollController) {
return Container(
color: Colors.red,
child: ListView.builder(
itemCount: 2,
itemBuilder: (BuildContext context, int index) {
return ListTile(title: Text('text'));
},
),
);
},
),
),
);
How can I make the contents of the sheet to start at the top? I have tried enclosing the listview with Align class and set the alignment to topcenter but same result.
You should remove your SizedBox and pass in expand: false to fix your immediate problem.
In addition, if you want a ListView to scroll with the DraggableScrollableSheet, you need to set isScrollControlled: true, and pass the controller to ListView as well.
Here is a full example of using a ListView as child:
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(home: MyHome()));
}
class MyHome extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('BottomSheet Demo')),
body: Center(
child: ElevatedButton(
child: Text('Show DraggableScrollableSheet'),
onPressed: () async {
final result = await showModalBottomSheet(
context: context,
isScrollControlled: true,
builder: (_) => DraggableScrollableSheet(
expand: false,
builder: (context, ScrollController controller) {
return ListView.builder(
controller: controller,
itemCount: 100,
itemExtent: 50,
itemBuilder: (context, index) {
return Center(child: Text('Item $index'));
},
);
},
),
);
print(result);
},
),
),
);
}
}
DraggableScrollableSheet(initialChildSize: 1.0,
builder: (BuildContext context, ScrollController scrollController) {

Calling API data, is accusing the [] was called on null

I'm trying to call an API, the console brings me the data, but on the screen it says that the method [] is being called as null.
How can I solve this?
body: Container(
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
child: StreamBuilder<ApiResponse<CategoryResponse>>(
stream: categoryBloc.subject.stream,
builder: (context, AsyncSnapshot<ApiResponse<CategoryResponse>> snapshot) {
if (snapshot.hasData) {
return ListView.builder(
itemCount: 5,
itemBuilder: (BuildContext context, int data) {
return Card(
child: Column(children: <Widget> [
ListTile(leading: CircleAvatar(
backgroundImage: NetworkImage(snapshot.data.data.Result.image_url),
),
title: Text(snapshot.data.data.Result.name),
),
],
),
);
},
);
} else if (snapshot.hasError) {
return Text("${snapshot.error}");
}
return Center(child: ColorLoader());
}
)
)
I'm following this method of assembling the methods and the like to call from the API: https://github.com/iamsahilarora/Flutter-clean-architecture
Look at the console:
Replace .data.data.Result with .data[data].Result.

How do I make it so that all filters don't change color when tapped on flutter

I'm trying to make a template for a filter that takes in one parameter (the tag name) and gets highlighted when tapped. But the problem with this is when one filter is tapped all of them change color because they all use the same boolean value. Sorry, I'm a beginner and I think I'm going about this the wrong way
class _HomeState extends State<Home> {
bool filterTap = true;
GestureDetector filterTemplate(String tag) {
return GestureDetector(
onTap: () {
setState(() {
filterTap = !filterTap;
});
},
child: Center(
child: Container(
margin: const EdgeInsets.only(right: 20.0),
padding: const EdgeInsets.symmetric(vertical: 5.0, horizontal: 10.0),
decoration: BoxDecoration(
border: Border.all(color: Colors.grey),
borderRadius: BorderRadius.all(Radius.circular(4.0)),
color: filterTap ? Colors.grey : Colors.transparent,
),
child: Text(
tag,
style: TextStyle(
color: filterTap ? Colors.grey[900] : Colors.grey,
letterSpacing: 2.0,
),
),
),
),
);
}
first of all define a StructFilter class with its properties, For example here is an option:
class StructFilter {
StructFilter(this.tag,this.filterTap);
String tag;
bool filterTap;
}
Then collect all of your filter information into a list of StructFilter(i.e List<StructFilter> filterList).
For example you can try:
Listview(
children: filterList.map((item){
return filterTemplate(item);
}).toList();
)
GestureDetector filterTemplate(StructFilter structFilter) {
return GestureDetector(
onTap: () {
setState(() {
structFilter.filterTap = !structFilter.filterTap;
});
},
),
);
}
Use List or Map or List<YourClass> to maintain status of each button.
And try ChoiceChip,
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(home: Home()));
}
class Home extends StatefulWidget {
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
Map<String, bool> tagsList = {
"Tag1": false,
"Tag2": false,
"Tag3": false,
"Tag4": false,
};
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Row(
children: tagsList.entries.map((entry) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: ChoiceChip(
label: Text(entry.key),
selected: entry.value,
onSelected: (value) {
setState(() {
tagsList[entry.key] = value;
});
},
),
);
}).toList(),
),
),
);
}
}

How can I implement search in this Flutter app

I'm trying to add a search feature to this flutter app since the json file it pulls data from has 7000 results.
Mainly I'm trying to do search for "ctry" and "peopnameincountry". This was ripped from https://www.youtube.com/watch?v=EwHMSxSWIvQ
As is .. it works fine in fetching the json list and the tap to show detail page works as well.
I just need to implement the search on the main page so I don't have to scroll through the thousands of results.
Appreciate any help .. thank you all.
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'dart:async';
import 'package:http/http.dart' as http;
import 'dart:convert';
void main() => runApp(new UnReached());
class UnReached extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new MyHomePage(title: 'Unreached'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
Future<List<User>> _getUsers() async {
var data = await http.get("https://cmfiflutterapp.s3-ap-southeast-2.amazonaws.com/UnreachedPeoplesGroup.json");
var jsonData = json.decode(data.body);
List<User> users = [];
for(var u in jsonData){
User user = User(u["ctry"], u["peopnameincountry"], u["population"], u["primarylanguagename"], u["biblestatus"], u["primaryreligion"], u["continent"]);
users.add(user);
}
print(users.length);
return users;
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: Text(widget.title),
),
body: Container(
child: FutureBuilder(
future: _getUsers(),
builder: (BuildContext context, AsyncSnapshot snapshot){
print(snapshot.data);
if(snapshot.data == null){
return Container(
child: Center(
child: Text("Loading...")
)
);
} else {
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (BuildContext context, int index) {
return ListTile(
leading: Icon(Icons.arrow_forward_ios),
// leading: CircleAvatar(
// backgroundImage: NetworkImage(
// snapshot.data[index].picture
// ),
// ),
title: Text(snapshot.data[index].peopnameincountry),
subtitle: Text(snapshot.data[index].ctry),
onTap: (){
Navigator.push(context,
new MaterialPageRoute(builder: (context) => DetailPage(snapshot.data[index]))
);
},
);
},
);
}
},
),
),
);
}
}
Try adding these function in your code:
import 'package:flutter/material.dart';
import 'dart:core';
class HomeScreen1 extends StatefulWidget {
#override
HomeScreenState createState() => HomeScreenState();
}
class HomeScreenState extends State<HomeScreen1> {
var searchController = new TextEditingController();
String search;
List<String> _filterList;
String _query = "";
bool _firstSearch = true;
#override
void initState() {
super.initState();
}
HomeScreenState() {
searchController.addListener(() {
if (searchController.text.isEmpty) {
setState(() {
_firstSearch = true;
_query = "";
});
} else {
setState(() {
_firstSearch = false;
_query = searchController.text;
});
}
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: new Container(
margin: EdgeInsets.only(left: 10.0, right: 10.0, top: 10.0),
child: new Column(
children: <Widget>[
_createSearchView(),
new Expanded(
child: _firstSearch ? _createListView() : _performSearch(),
),
],
),
),
);
}
Widget _createSearchView() {
return new Container(
decoration: BoxDecoration(border: Border.all(width: 1.0)),
child: new TextField(
controller: searchController,
decoration: InputDecoration(
icon: Icon(Icons.search),
hintText: "Search",
hintStyle: new TextStyle(color: Colors.grey[300]),
),
//textAlign: TextAlign.center,
),
);
}
Widget _createListView() {
return FutureBuilder(
future: _getUsers(),
builder: (BuildContext context, AsyncSnapshot snapshot){
print(snapshot.data);
if(snapshot.data == null){
return Container(
child: Center(
child: Text("Loading...")
)
);
} else {
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (BuildContext context, int index) {
return ListTile(
leading: Icon(Icons.arrow_forward_ios),
// leading: CircleAvatar(
// backgroundImage: NetworkImage(
// snapshot.data[index].picture
// ),
// ),
title: Text(snapshot.data[index].peopnameincountry),
subtitle: Text(snapshot.data[index].ctry),
onTap: (){
Navigator.push(context,
new MaterialPageRoute(builder: (context) => DetailPage(snapshot.data[index]))
);
},
);
},
);
}
},
),
}
Widget _performSearch() {
return FutureBuilder<List>(builder: (context, snapshot) {
_filterList = new List<String>();
for (int i = 0; i < snapshot.data.length; i++) {
var item = snapshot.data[i];
if ((item.toString().toLowerCase()).contains(_query.toLowerCase())) {
_filterList.add(item.toString());
}
}
return _createFilteredListView();
});
}
Widget _createFilteredListView() {
return ListView.builder(
itemCount: _filterList.length,
itemBuilder: (BuildContext context, int index) {
return new Card(
color: Colors.white,
elevation: 5.0,
child: new Container(
margin: EdgeInsets.all(15.0),
child: new Text("${_filterList[index]}"),
),
);
});
}
}
The concept of a FutureBuilder widget is to be build as soon as it is received, but meanwhile, the snapshot contains no data at all. So when you're calling :
for (int i = 0; i < snapshot.data.length; i++) {
you're, at least at first, calling length on null since the data is not yet received.
The solution is to create a switch and call `snapshot.data when the status is completed:
switch (snapshot.connectionState) {
case ConnectionState.none:
return DefaultWidget(); // For instance a CircularProgress
case ConnectionState.active:
return DefaultWidget(); // For instance a CircularProgress
case ConnectionState.waiting:
return DefaultWidget(); // For instance a CircularProgress
case ConnectionState.done:
if (snapshot.hasError)
return ErrorWidget('Error: ${snapshot.error}'); //For example a Text Widget
// Your code here:
_filterList = new List<String>();
for (int i = 0; i < snapshot.data.length; i++) {
var item = snapshot.data[i];
if ((item.toString().toLowerCase()).contains(_query.toLowerCase())) {
_filterList.add(item.toString());
}
}
return _createFilteredListView();
}
return null; // unreachable
More on this here
My apologies guys...I ended up taking a slightly different approach which I thought was slightly faster in response than the FutureBuilder approach. Maybe it's just my internet. Not sure.
import 'package:flutter/material.dart';
import 'dart:core';
import 'package:http/http.dart' as http;
import 'dart:convert';
import 'dart:async';
import 'package:progress_indicators/progress_indicators.dart';
class IslandWaves extends StatefulWidget {
#override
HomeScreenState createState() => HomeScreenState();
}
class HomeScreenState extends State<IslandWaves> {
List<User> _list = [];
List<User> _search = [];
var loading = false;
Future<Null> fetchData() async {
setState(() {
loading = true;
});
_list.clear();
final response = await http.get(
"https://cmfiflutterapp.s3-ap-southeast-2.amazonaws.com/UnreachedPeoplesGroup.json");
if (response.statusCode == 200) {
final data = jsonDecode(response.body);
setState(() {
for (Map i in data) {
_list.add(User.formJson(i));
loading = false;
}
});
}
}
TextEditingController controller = new TextEditingController();
onSearch(String text) async {
_search.clear();
if (text.isEmpty) {
setState(() {});
return;
}
_list.forEach((f) {
if (f.ctry.contains(text) ||
f.peopnameincountry.toString().contains(text)) _search.add(f);
});
setState(() {});
}
#override
void initState() {
// TODO: implement initState
super.initState();
fetchData();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Container(
child: Column(
children: <Widget>[
Container(
padding: EdgeInsets.all(10.0),
color: Colors.blue,
child: Card(
child: ListTile(
leading: Icon(Icons.search),
title: TextField(
controller: controller,
onChanged: onSearch,
decoration: InputDecoration(
hintText: "Search", border: InputBorder.none),
),
trailing: IconButton(
onPressed: () {
controller.clear();
onSearch('');
},
icon: Icon(Icons.cancel),
),
),
),
),
loading
? Center(
heightFactor: 20.0,
child: FadingText('Loading...'),
)
: Expanded(
child: _search.length != 0 || controller.text.isNotEmpty
? ListView.builder(
itemCount: _search.length,
itemBuilder: (context, i) {
final b = _search[i];
return GestureDetector(
onTap: () {
Navigator.push(
context,
new MaterialPageRoute(
builder: (context) =>
DetailPage(_search[i])));
debugPrint('TopNav');
},
child: Container(
padding: EdgeInsets.all(10.0),
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: <Widget>[
Text(
b.ctry,
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 18.0),
),
SizedBox(
height: 4.0,
),
Text(b.peopnameincountry),
],
)),
);
},
)
: ListView.builder(
itemCount: _list.length,
itemBuilder: (context, i) {
final a = _list[i];
return GestureDetector(
onTap: () {
Navigator.push(
context,
new MaterialPageRoute(
builder: (context) =>
DetailPage(_list[i])));
debugPrint('BottomNav');
},
child: Container(
padding: EdgeInsets.all(10.0),
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: <Widget>[
Text(
a.ctry,
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 18.0),
),
SizedBox(
height: 4.0,
),
Text(a.peopnameincountry),
],
)
),
);
},
),
),
],
),
),
);
}
}
class DetailPage extends StatelessWidget{....etc.}

Resources