I am a beginner, and I am building an app following a tutorial using provider. The logic is that, when You click the "Categories" widget, it should lead you to a page with products in that category. The logic works as expected except I have to first refresh the code for results to show. I frankly do not know what I am doing wrong.
From the Homepage, the Categories widget is rendered using a listview builder
Container(
height: 75,
child: ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: categoryProvider.categories.length,
itemBuilder: (context, index) {
return GestureDetector(
onTap: () async {
await productProvider.loadProductsByCategory(
categoryName:
categoryProvider.categories[index].name);
changeScreen(
context,
CategoryScreen(
categoryModel:
categoryProvider.categories[index]));
},
child: CategoriesWidget(
category: categoryProvider.categories[index]),
);
},
),
)
I am listening to providers like this
void main() {
WidgetsFlutterBinding.ensureInitialized();
runApp(MultiProvider(
providers: [
ChangeNotifierProvider.value(value: AppProvider()),
ChangeNotifierProvider.value(value: UserProvider.initialize()),
ChangeNotifierProvider.value(value: CategoryProvider.initialize()),
ChangeNotifierProvider.value(value: MarketProvider.initialize()),
ChangeNotifierProvider.value(value: ProductProvider.initialize()),
],
child: MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Akatale',
theme: ThemeData(
primaryColor: Colors.black,
),
home: ScreenController())));
}
class ScreenController extends StatelessWidget {
@override
Widget build(BuildContext context) {
final auth = Provider.of<UserProvider>(context);
switch (auth.status) {
case Status.Uninitialized:
return Loading();
case Status.UnAuthenticated:
case Status.Authenticating:
return SignInScreen();
case Status.Authenticated:
return MainScreen();
default:
return SignInScreen();
}
}
}
With this, I fetch products from firestore by the name of the category and add them to a List
class ProductService extends ChangeNotifier {
String collection = "products";
Firestore _firestore = Firestore.instance;
Future<List<ProductModel>> getProductsByCategory({String category}) async {
List<ProductModel> products = [];
_firestore
.collection(collection)
.where("category", isEqualTo: category)
.getDocuments()
.then((result) {
for (DocumentSnapshot product in result.documents) {
products.add(ProductModel.fromSnapshot(product));
}
});
return products;
}
}
Here I have public methods to be called in widgets
class ProductProvider with ChangeNotifier {
ProductService _productService = ProductService();
List<ProductModel> products = [];
List<ProductModel> productsByCategory = [];
ProductProvider.initialize() {
_loadProducts();
}
//Private method Load products to List
Future _loadProducts() async {
products = await _productService.getProducts();
notifyListeners();
}
//public method to Load products to List by category
Future loadProductsByCategory({String categoryName}) async {
productsByCategory =
await _productService.getProductsByCategory(category: categoryName);
notifyListeners();
}
}
This is where products are rendered based on the chosen category
class CategoryScreen extends StatelessWidget {
final CategoryModel categoryModel;
const CategoryScreen({Key key, this.categoryModel}) : super(key: key);
@override
Widget build(BuildContext context) {
final productProvider = Provider.of<ProductProvider>(context);
return Scaffold(
body: SafeArea(
child: ListView(
children: [
Text(categoryModel.name),
Column(
children: productProvider.productsByCategory.map((item) {
return GestureDetector(
onTap: () {
//Load details page
},
child: ProductWidget(
product: item,
));
}).toList(),
),
],
)),
);
}
}
In getProductsByCategory
in your ProductsService
you are not awaiting getDocuments
.
So while the documents will get loaded it already calls notifyListeners in the model before it is actually done loading them.
And thus when you rebuild using hotreload they show up
To fix this simply await the getDocuments in the service