Flutter build TabBarView items via server response - flutter

in one of our mobile application we have a simple screen which we want to make TabBarView pages via result of server response, for example if server response this collection:
{
"categories": [
{
/* has 3 children */
"category_item": {
"id": 1,
"title": "sample"
},
"products": [
{
"id": 1,
"title": "sample 1"
},
{
"id": 2,
"title": "sample 1"
},
{
"id": 3,
"title": "sample 1"
}
]
},
/* has 2 children */
{
"category_item": {
"id": 2,
"title": "test"
},
"products": [
{
"id": 1,
"title": "test 1"
},
{
"id": 2,
"title": "test 1"
}
]
}
]
}
we should have 2 Tabs as sample and test with some children inside TabBarView relatives with parent category, for example first index of array has 3 children and second index of array has 2 children.
our problem is TabController that when we retrieve data from server, screen has been built and we can't manage this action. I mean this tab and TabBarView children should be built with response data. basically time I passed zero to TabController because server doesn't response in this time and after getting data I get error on length of that,
class SelectClothesScreenState extends State<SelectClothesScreen> with SingleTickerProviderStateMixin {
TabController _tabController;
CategoryProducts _cats;
List<Products> _productList = [];
#override
void initState() {
super.initState();
_getCats();
}
bool _isLoading = true;
_getCats() async {
final CategoryProducts response = await Api.apiCatList();
List<Products> _p = [];
setState(() {
_cats = response;
_productList = _p;
_isLoading = false;
});
_tabController = TabController(length: _cats == null ? 0 : _cats.categories.length ?? 0, vsync: this);
}
Widget loadingView() {
return CircularProgressIndicator();
}
#override
Widget build(BuildContext context) {
var fullWidth = MediaQuery.of(context).size.width;
return Scaffold(
resizeToAvoidBottomInset: false,
body: Column(
children: [
//address and search icon and close icon
Container(
height: 50,
child: _cats != null && _isLoading
? loadingView()
: ListView.builder(
shrinkWrap: true,
padding: EdgeInsets.all(8),
itemCount: _cats == null ? 0 : _cats.categories.length,
scrollDirection: Axis.horizontal,
itemBuilder: (BuildContext context, int index) {
print(_cats.categories.length);
return GestureDetector(
onTap: () {
print(index);
_tabController.animateTo(index, duration: Duration(milliseconds: 750), curve: Curves.ease);
},
child: CatItem(catView: _cats.categories[index].categoryItem),
);
},
),
),
//content grid list product and btn confirm
Container(
width: fullWidth,
child: TabBarView(controller: _tabController, children: [
//grid product list
...List.generate(_cats == null ? 0 : _cats.categories.length, (index) {
return Container(
width: fullWidth,
child: GridView.builder(
shrinkWrap: true,
itemCount: _productList.length,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2),
itemBuilder: (BuildContext context, int index) {
return Container(color: Colors.black);
//return ProductView(productView: _productList[index]);
},
),
);
}),
]),
),
],
));
}
#override
void dispose() {
super.dispose();
_tabController.dispose();
}
}
how can I set correct implementation for length of TabController after retrieving data from server? thanks in advance

Related

Flutter Dynamic TabBar for data coming from FireStore Collection

I have a FireStore collection named "products" and in there I have documents consisting of product data such as name, price, and category. It follows the structure like this
{"name": "Milk Shake Strawberry",
"price": "250",
"category": "Drinks"
},
{"name": "Swiss Roll",
"price": "150",
"category": "Cake"
}
.
I want to create the UI to show Each category as a Tab (ex: Drinks Tab, Cake Tab) and inside that tab, I want to show products related to that exact category.
How can I achieve this in Flutter?
Try this
class ProductCategoryTabs extends StatefulWidget {
#override
_ProductCategoryTabsState createState() => _ProductCategoryTabsState();
}
class _ProductCategoryTabsState extends State<ProductCategoryTabs> {
List<String> _tabs = [];
Map<String, List<Product>> _products = {};
#override
void initState() {
super.initState();
// Fetch the list of categories and products from Firestore
Firestore.instance.collection('products').getDocuments().then((snapshot) {
snapshot.documents.forEach((document) {
var product = Product.fromFirestore(document);
if (!_tabs.contains(product.category)) {
_tabs.add(product.category);
}
if (_products[product.category] == null) {
_products[product.category] = [product];
} else {
_products[product.category].add(product);
}
});
setState(() {});
});
}
#override
Widget build(BuildContext context) {
return DefaultTabController(
length: _tabs.length,
child: Scaffold(
appBar: AppBar(
bottom: TabBar(
tabs: _tabs.map((String tab) {
return Tab(text: tab);
}).toList(),
),
),
body: TabBarView(
children: _tabs.map((String tab) {
return ListView.builder(
itemCount: _products[tab].length,
itemBuilder: (BuildContext context, int index) {
return ProductTile(product: _products[tab][index]);
},
);
}).toList(),
),
),
);
}
}

The improper use of a GetX has been detected when using RxList

I have created an observable list and then storing data in the list from the api as below
class FeedsController extends GetxController {
final Rx<List<Stories>> _stories = Rx<List<Stories>>([]);
#override
void onInit() {
super.onInit();
getActiveStories();
}
List<Stories> get getStories {
return _stories.value;
}
Future<List<Stories>> getActiveStories() async {
var url = Uri.parse(storiesURL);
Map<String, Object> params = {'apikey': apiKey, 'userid': "8"};
await http.post(url, body: params).then((value) {
StoriesResponse storiesResponse = storiesResponseFromJson(value.body);
_stories.value = storiesResponse.stories;
}).onError((error, stackTrace) {
debugPrint('Error occurred while fetching stories response: ${error.toString()}');
});
return _stories.value;
}
}
Here is the code for displaying the Stories List
class _ActiveStoriesListState extends State<ActiveStoriesList> {
List<Story> _currentUserStories = [];
final FeedsController _feedsController = Get.find();
#override
void initState() {
Future.delayed(Duration.zero, fetchUserStories);
super.initState();
}
#override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Active Stories',
style: titleLargeTextStyle.copyWith(fontSize: 22, fontWeight: FontWeight.w600),),
const SizedBox(height: 10),
SizedBox(
height: 100,
child: Obx(
() => ListView.builder(
shrinkWrap: true,
scrollDirection: Axis.horizontal,
itemBuilder: (ctx, index) =>
ActiveStoriesWidget(story: _currentUserStories[index]),
itemCount: _currentUserStories.length,
),
)),
],
);
}
void fetchUserStories() async {
List<Stories> stories = _feedsController.getStories;
stories = stories.where((story) => story.userId == '8').toList();
_currentUserStories = stories[0].story;
debugPrint('Active Stories Page: ${_currentUserStories.length}');
}
}
Solutions I have tried is that make only ListView observable (that didn't work), making ListView parent child Observable that also didn't work. I'm unable to understand where I'm missing. Below is the exception
Exception Screenshot
sample json data
{ "status": 200, "success": [ { "user_id": "4", "first_name": "Abu", "profileImage": "jayspur.com/RallyApp/public/uploads/userImages/…", "story": [ { "story": "jayspur.com/RallyApp/public/uploads/userStories/…", "addeddate": "2023-02-08 09:58:11" } ] } ] }
You are getting this error because you are not using any observable list inside your ListView.builder.
But before that you should convert your StatefullWidget to a StatelessWidget because in GetX, we don't need any StatefullWidget.
You can try the following code.
Controller
class FeedsController extends GetxController {
final Rx<List<Stories>> _stories = Rx<List<Stories>>([]);
List<Stories> currUserstories = [];
RxBool isLoading = false.obs;
#override
void onInit() {
super.onInit();
getActiveStories();
}
List<Stories> get getStories {
return _stories.value;
}
Future<List<Stories>> getActiveStories() async {
isLoading.value = true;
var url = Uri.parse("storiesURL");
Map<String, Object> params = {'apikey': apiKey, 'userid': "8"};
await http.post(url, body: params).then((value) {
StoriesResponse storiesResponse = storiesResponseFromJson(value.body);
_stories.value = storiesResponse.stories;
_stories.value =
_stories.value.where((story) => story.userId == '8').toList();
currUserstories = _stories.value[0];
}).onError((error, stackTrace) {
debugPrint(
'Error occurred while fetching stories response: ${error.toString()}');
});
isLoading.value = false;
return _stories.value;
}
}
View file:
class ActiveStoriesList extends StatelessWidget {
ActiveStoriesList({super.key});
final FeedsController _feedsController = Get.find();
#override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Active Stories',
style: titleLargeTextStyle.copyWith(
fontSize: 22, fontWeight: FontWeight.w600),
),
const SizedBox(height: 10),
SizedBox(
height: 100,
child: Obx(
() => _feedsController.isLoading.value
? const Center(
child: CircularProgressIndicator(),
)
: ListView.builder(
shrinkWrap: true,
scrollDirection: Axis.horizontal,
itemBuilder: (ctx, index) => ActiveStoriesWidget(
story: _feedsController.currUserstories[index]),
itemCount: _feedsController.currUserstories.length,
),
)),
],
);
}
}
You might have to tweak the code a bit but the core concept it you should do all your work inside the controller and only fetch the data in view file.
Also, you should only use the lifecycle which controller provides. Eg. onInit instead of initState.
If this dosen't work, try to modify your controller file such that you get the value in the list as you want in the controller file itself and you the list which was preseneted in controller in your view file.
Hope it helps.

Flutter Lazy loading on Listview inside another Listview

How to apply Lazy Loading or other better option to load more than 5000 of topping list in the Listview.builder inside another Listview.builder?
Below is post.json which will be loaded from local assets
{
"Food": [
{
"name": "Cake",
"id": "0001",
"description": [
{
"category": "AAA",
"Size": "Regular",
"topping": []
},
{
"category": "BBB",
"Size": "Small",
"topping": []
},
{
"category": "CCC",
"Size": "Medium",
"topping": [
{
"ingredient": "Chocolate with Sprinkles",
"price": "$70"
},
{
"ingredient": "Maple",
"price": "$99"
},
{
"ingredient": "Blueberry",
"price": "$123"
}, ... // more than 5000 of topping list
]
}
]
},
{
"name": "Raised",
"id": "0002",
"description": ... // same structure as above
}
]
}
Below is the Main file to display List of foods
void main() {
runApp(const MyApp());
}
// Load local Json file
Future<Post> getFoodList() => Future.delayed(Duration(seconds: 1), () async {
final getResponse = await rootBundle.loadString('assets/post.json');
var data = jsonDecode(getResponse);
return Post.fromJson(data);
});
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
Expanded(
child: FutureBuilder<Post>(
future: getFoodList(),
builder: (context, snapshot) {
if (snapshot.hasData) {
var getFoodName = snapshot.data!.food;
return SingleChildScrollView(
child: Wrap(
children: [
for (final food in getFoodName!)
GestureDetector(
child: Text(foodName.name.toString()),
onTap: () {
Navigator.push(context, MaterialPageRoute(builder: (context) => foodDetail(food: food)));
},
),
],
),
);
} else {
return Text('Loading');
}
},
),
),
],
),
);
}
}
Food Detail page - display more than 5000 of topping list
class FoodDetail extends StatefulWidget {
final Food food;
const FoodDetail({super.key, required this.food});
#override
State<FoodDetail> createState() => _FoodDetailState();
}
class _FoodDetailState extends State<FoodDetail> {
late ScrollController controller;
// This example is taken from Flutter ListView lazy loading
List<String> items = List.generate(100, (position) => 'Hello $position');
#override
void initState() {
super.initState();
controller = ScrollController()..addListener(_scrollListener);
}
#override
void dispose() {
controller.removeListener(_scrollListener);
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Padding(
padding: const EdgeInsets.all(8.0),
child: SingleChildScrollView(
child: Column(
children: [
// This is the first ListView.build to display category and size under description
ListView.builder(
itemCount: widget.food.description!.length,
itemBuilder: (context, index) {
// IF category is AAA || BBB, display the size
if (widget.food.description![index].category == "AAA" || widget.food.description![index].category == "BBB") {
return Text(widget.food.description![index].size.toString());
}
// IF category is CCC display the List of Topping (Note: this topping list is more than 5000)
else {
// This is the second ListView.build to display the topping information
// How do I apply ScollController or Lazy Loading to load 5000 list of topping?
return ListView.builder(
controller: controller,
itemCount: widget.food.description![index].topping!.length,
itemBuilder: (context, position) {
return Column(
children: [
Text(widget.food.description![index].topping![position].ingredient.toString())
Text(widget.food.description![index].topping![position].price.toString())
],
);
});
}
}),
],
),
)));
}
// This example is taken from Flutter ListView lazy loading
void _scrollListener() {
if (controller.position.extentAfter < 500) {
setState(() {
items.addAll(List.generate(42, (index) => 'Inserted $index'));
});
}
}
}
Output of the workflow:
I think your best bet is to try to try to refactor it in a way to use only a single ListView.builder. To do this you need to flatten the data you are working with in some way. Here is an example that also uses nested data that transforms it in a way so a single ListView displays it. I'm not sure if it's the best way to do it but I think you could do something similar for your code maybe. This might give you an idea of how to do it:
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
final data = const [
{
"name": "A",
"subnames": ["A1", "A2", "A3", "A4", "A5"]
},
{
"name": "B",
"subnames": ["B1", "B2", "B3", "B4", "B5"]
},
{
"name": "C",
"subnames": ["C1", "C2", "C3", "C4", "C5"]
},
];
const MyApp({super.key});
#override
Widget build(BuildContext context) {
final newData = data
.map((e) => [
{"type": 1, "name": e["name"]},
for (final name in (e["subnames"] as List))
{"type": 2, "name": name}
])
.expand((e) => e.toList())
.toList();
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Padding(
padding: const EdgeInsets.all(8.0),
child: ListView.builder(
itemCount: newData.length,
itemBuilder: (context, index) {
if (newData[index]['type'] == 1) {
return Text(
newData[index]['name'],
style: const TextStyle(color: Colors.red),
);
} else {
return Text(
newData[index]['name'],
style: const TextStyle(color: Colors.blue),
);
}
}),
)),
);
}
}
Output:

Flutter Provider : cannot remove item from list in provider

I have checkbox listtile inside listview builder I want when I check any one then its data add to list then when I uncheck it be removed from list:
Directionality(
textDirection: TextDirection.rtl,
child: ListView.builder(
itemCount: student.length,
itemBuilder: (context, index) {
return Card(
child: CheckBoxedListTile(
student[index], widget.date, widget.time,widget.teacher,widget.subject));
}),
),
);
}
}
check listtile widget is :
class _CheckBoxedListTileState extends State<CheckBoxedListTile> {
var checked;
#override
void initState() {
checked = false;
super.initState();
}
#override
Widget build(BuildContext context) {
return Consumer<AbsenceProvider>(builder: (context, absProv, child) {
return CheckboxListTile(
value: checked,
onChanged: (val) {
setState(() {
checked = !checked;
});
var data = {
"name": widget.student.name,
"stage": widget.student.stage,
"group": widget.student.group,
"teacher": widget.teacher,
"subject": widget.subject,
"date": widget.date,
"time": widget.time,
"vacs": "No"
};
if (checked == true) {
absProv.addAbs(data);
} else {
absProv.remAbs(data);
}
print(absProv.absences);
},
title: Text('${widget.student.name}'),
);
});
}
}
provider is :
class AbsenceProvider with ChangeNotifier {
var absences = [];
addAbs(item) {
absences.add(item);
notifyListeners();
}
remAbs(item) {
absences.remove(item);
notifyListeners();
}
}
when I click on check box it is add successfully
but when i click again it is nor remove it
I solved it by using removeWhere :
before :
absences.remove(item);
after :
absences.removeWhere((e) => e['name'] == item['name']);

Flutter: How to filter data from JSON based on TabBar and ToggleButtons?

I have a JSON like this:
[
{
"continentName": "NA",
"isDayTime": true,
"seasonName": "Spring",
"cityName": "United States",
"xAlign": 45.4,
"yAlign": 69,
"cityTemperature": 27
},
{
"continentName": "NA",
"isDayTime": true,
"seasonName": "Spring",
"cityName": "Canada",
"xAlign": 35.7,
"yAlign": 53,
"cityTemperature": 16
},
{
"continentName": "NA",
"isDayTime": true,
"seasonName": "Summer",
"cityName": "Mexico",
"xAlign": 87.8,
"yAlign": 41.8,
"cityTemperature": 28
},
{
"continentName": "NA",
"isDayTime": false,
"seasonName": "Summer",
"cityName": "Cuba",
"xAlign": 55.3,
"yAlign": 88.8,
"cityTemperature": 27
},
{
"continentName": "EU",
"isDayTime": true,
"seasonName": "Winter",
"cityName": "Germany",
"xAlign": 33.8,
"yAlign": 38.8,
"cityTemperature": 3
}
]
I want to display the filtered data as follows:
The 1st filter is TabBar ("continentName")
The 2nd filter is ToggleButtons ("isDayTime") => requiring at least one selection
The 3rd filter is ToggleButtons ("listSeason") => mutually exclusive selection, but allows for none of the buttons to be selected.
When start the page, by default, Tabbar is selected as "NA", the first toggleButtons ("isDayTime") is selected as "Day" => I want that if click on "Spring" => it will display the satisfying data, specifically here will be "United States" and "Canada"
So please help me, this is main file:
import 'package:ask/model/temperature_model.dart';
import 'package:ask/services/temperature_service.dart';
import 'package:flutter/material.dart';
class CityTemperature extends StatefulWidget {
CityTemperature() : super();
#override
_CityTemperatureState createState() => _CityTemperatureState();
}
class _CityTemperatureState extends State<CityTemperature> {
List<Temperature> _temperature = [];
List<bool> isDayTime = [true, false];
List<bool> listSeason = [false, false, false, false];
#override
void initState() {
super.initState();
TemperatureServices.getTemperature().then((temperature) {
setState(() {
_temperature = temperature;
});
});
}
#override
Widget build(BuildContext context) {
return Container(
child: DefaultTabController(
length: 4,
child: Scaffold(
appBar: AppBar(
title: Text('Temperature'),
bottom: TabBar(tabs: [
Tab(child: Text('NA')),
Tab(child: Text('EU')),
Tab(child: Text('Africa')),
Tab(child: Text('Asia')),
]),
),
body: Column(children: [
Center(
child: ToggleButtons(
children: [Text('Day'), Text('Night')],
onPressed: (int index) {
setState(() {
for (int buttonIndex = 0; buttonIndex < isDayTime.length; buttonIndex++) {
if (buttonIndex == index) {
isDayTime[buttonIndex] = true;
} else {
isDayTime[buttonIndex] = false;
}
}
});
},
isSelected: isDayTime)),
SizedBox(height: 5),
Center(
child: ToggleButtons(
children: [Text('Spring'), Text('Summer'), Text('Autumn'), Text('Winter')],
onPressed: (int index) {
setState(() {
for (int buttonIndex = 0; buttonIndex < listSeason.length; buttonIndex++) {
if (buttonIndex == index) {
listSeason[buttonIndex] = !listSeason[buttonIndex];
} else {
listSeason[buttonIndex] = false;
}
}
});
},
isSelected: listSeason)),
SizedBox(height: 5),
Expanded(
child: TabBarView(children: [
Column(children: [ // How to display the satisfying data
for (Temperature temp in _temperature)
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Text(temp.cityName),
Text('${temp.cityTemperature.toString()}° C'),
],
)
]),
Column(), // How to display the satisfying data
Column(), // How to display the satisfying data
Column(), // How to display the satisfying data
]),
)
]))));
}
}
Edit 1:
I want to add 2 things as follows:
1. Add background image in TabBarView for each _tabs and each
isDayTime
For each continentName, there will be 2 images for Day or Night.
Because it is an image, I think I will put it in Assets for users to load faster. Besides, to avoid creating more data on json => I will create the filename of image as: "na_day.png" or "na_true.png" and access it by: Image.asset('assets/${temp.continentName}_${isDayTime}.png') or something like that
2. Display cityName on background image based on X Y percent position of image
I use data from JSON: xAlign & yAlign to determine the position of cityName on the image (JSON updated)
As far as I know, it seems the best way is used IntrinsicHeight, Stack and Align to do like this:
class DisplayCountry extends StatelessWidget {
final List<Temperature> countries;
DisplayCountry({this.countries});
#override
Widget build(BuildContext context) {
return Column(children: [
for (Temperature temp in countries) // I don't know where to put this
IntrinsicHeight(
child: Stack(children: [
Image.asset('assets/${temp.continentName}_${isDayTime}.png'.asset), // Or something like this
Align(
alignment: Alignment(temp.xAlign / 100 * 2 - 1, temp.yAlign / 100 * 2 - 1),
child: Text(temp.cityName),
),
]),
)
]);
}
}
extension AssetsExtension on String {
String get asset => this.toLowerCase().replaceAll(" ", "_").replaceAll("'", "_");
}
So please help me update class DisplayCountry to be able to combine the 2 things above
something like this
class CityTemperature extends StatefulWidget {
CityTemperature() : super();
#override
_CityTemperatureState createState() => _CityTemperatureState();
}
class _CityTemperatureState extends State<CityTemperature> {
List<Temperature> _temperature = [];
List<String> _tabs = [];
Map<String, bool> isDayTime = {'Day': true, 'Night': false};
Map<String, bool> listSeason = {'Spring': false, 'Summer': false, 'Autumn': false, 'Winter': true};
#override
void initState() {
super.initState();
var response = json.decode(jsonFile);
_temperature = List<Temperature>.from(response.map((x) => Temperature.fromJson(x)));
_tabs = _temperature.map<String>((x) => x.continentName).toSet().toList();
/*
TemperatureServices.getTemperature().then((temperature) {
setState(() {
_temperature = temperature;
});
});*/
}
#override
Widget build(BuildContext context) {
return DefaultTabController(
length: _tabs.length,
child: Scaffold(
appBar: AppBar(
title: Text('Temperature'),
bottom: TabBar(
tabs: _tabs.map((String name) => Tab(text: name)).toList()
),
),
body: Column(children: [
Center(
child: ToggleButtons(
children: isDayTime.keys.map((key) => Text(key)).toList(),
onPressed: (int index) {
String indexKey = isDayTime.keys.toList()[index];
setState(() {
isDayTime.updateAll(
(key, value) => key == indexKey ? true : false);
}
);
},
isSelected: isDayTime.values.toList())),
SizedBox(height: 5),
Center(
child: ToggleButtons(
children: listSeason.keys.map((key) => Text(key)).toList(),
onPressed: (int index) {
String indexKey = listSeason.keys.toList()[index];
setState(() {
listSeason.updateAll(
(key, value) => key == indexKey ?
!listSeason[indexKey] : false);
});
},
isSelected: listSeason.values.toList())),
SizedBox(height: 5),
Expanded(
child: TabBarView(
children: _tabs.map((String name) {
return DisplayCountry(
countries: List<Temperature>.from(_temperature)
..retainWhere((temperature) =>
temperature.continentName == name
&& temperature.isDayTime == isDayTime['Day']
&& temperature.seasonName == listSeason.keys.firstWhere(
(k) => listSeason[k] == true, orElse: () => 'Nothing'))
);
}).toList()
),
)
]
)
)
);
}
}
class DisplayCountry extends StatelessWidget{
final List<Temperature> countries;
DisplayCountry({this.countries});
#override
Widget build(BuildContext context){
return Column(
children: [
for(Temperature temp in countries)
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Text(temp.cityName),
Text('${temp.cityTemperature.toString()}° C'),
],
)
]
);
}
}
I create a list called _tabs with all the continentName of _temperatures, then added toSet and toList. toSet converts it to a set, a set is an iterable that doesn't allow repeated values, and then I converted it back to list, that way I have a list of unique continentName (NA, EU, etc).
In DefaultTabController I add _tabs.length and in tabView I create a list of _tab.map, which creates a list of widgets of DisplayCountry, I use retainwhere to keep only the ones that satisfies the conditions (same continentName that the one in the tab, same seasonName that the one selected and isDayTime if it's true is day else night)
UPDATE
class DisplayImage extends StatelessWidget {
final List<Temperature> countries;
final String continentName;
final bool isDayTime;
DisplayImage({this.countries , this.continentName, this.isDayTime});
#override
Widget build(BuildContext context) {
final size = MediaQuery.of(context).size;
return Stack(
fit: StackFit.passthrough,
children: [
Image.asset('assets/$continentName_$isDayTime.png'.asset),
fit: BoxFit.cover,),
for (Temperature temp in countries)
Positioned(
left: temp.xAlign * size.width / 100.0,
top: temp.yAlign / 2 * size.height / 100.0,
child: Text('${temp.cityName} ${temp.cityTemperature.toString()}° C'),
)
]);
}
}
and when calling it in the TabView
TabBarView(
children: _tabs.map((String name) {
return DisplayImage(
continentName: name,
isDayTime: isDayTime['Day'],
countries: List<Temperature>.from(_temperature)
..retainWhere((temperature) =>
temperature.continentName == name &&
temperature.isDayTime == isDayTime['Day'] &&
temperature.seasonName ==
listSeason.keys.firstWhere(
(k) => listSeason[k] == true,
orElse: () => 'Nothing')));
}).toList())
As far as I understand you can use the fit property of the stack (StackFit.passthrough) and it will work the same as intrinsicHeight. From the documentation
StackFit.passthrough
For example, if a Stack is an Expanded child of a Row, the horizontal constraints will be tight and the vertical constraints will be loose.
In this case you're using an Expanded in a column so it has horizontal loose and vertical tight. Then do some math and try the Align widget if the positioned doesnt work as you want