flutterdarthivefavoriteshivedb

Troubles with making a Favorite page with Hive DB Flutter


Hello everyone here's my test app and I have some problems with making a Favorite page section where you can tap on button and add the item into fav page. I'm receiving a data from API and implementing it by Listview.builder Here are some photos of how it should look like: Home page

Favorite page

main.dart, here I'm openning a box called 'favorites_box'

import 'package:get/get.dart';
import 'package:get_storage/get_storage.dart';
import 'package:hive/hive.dart';
import 'package:hive_flutter/hive_flutter.dart';


void main() async{
  await GetStorage.init();
  await Hive.openBox('favorites_box');
  runApp(MainPage());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return GetMaterialApp(
      initialRoute: '/',
      getPages: [
        GetPage(name: '/', page: () => MyApp()),
        GetPage(name: '/main-page', page: () => MainPage()),
        GetPage(name: '/favorite_page', page: () => FavoritePage()),
        // Dynamic route
      ],
      home: MainPage(),
    );
  }
}

Well here's a code of home page: main_page.dart

import 'package:flutter/material.dart';
import '../View/listview_api.dart';
    
class MainPage extends StatefulWidget {
  @override
  State<MainPage> createState() => _MainPageState();
}

class _MainPageState extends State<MainPage> {
  int currentIndex = 0;

  List<BottomNavigationBarItem>? items;

  final screens = [
    HomePage(),
    HomePage()
    FavoritePage(),
    HomePage()
  ];

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: SafeArea(
        child: Scaffold(
          appBar: AppBar(
            backgroundColor: Colors.white,
            title: Container(
              width: double.infinity,
              height: 40,
              color: Colors.white,
              child: Center(
                child: TextField(
                  decoration: InputDecoration(
                    border: OutlineInputBorder(

                    ),
                      hintText: 'Searching',
                      prefixIcon: Icon(Icons.search),
                      suffixIcon: Icon(Icons.notifications)),
                ),
              ),
            ),
          ),
          body: screens[currentIndex],
          bottomNavigationBar: BottomNavigationBar(
          unselectedItemColor: Colors.grey,//AppColors.unselectedBottomNavItem,
          selectedItemColor: Colors.blue,//AppColors.assets,
          onTap: (index) => setState(() {
            currentIndex = index;
          }),//controller.setMenu(BottomMenu.values[pos]),
          //currentIndex: ,//controller.bottomMenu.index,
          type: BottomNavigationBarType.fixed,
          backgroundColor: Colors.white,
          currentIndex: currentIndex,
          selectedLabelStyle: const TextStyle(
            fontSize: 10,
            fontWeight: FontWeight.w500,
          ),
          unselectedLabelStyle: const TextStyle(
            fontSize: 10,
            fontWeight: FontWeight.w500,
          ),
          elevation: 8,
            items: [
              BottomNavigationBarItem(
                icon: Icon(Icons.home),
                label: 'Home',
                backgroundColor: Colors.blue,
              ),
              BottomNavigationBarItem(
                icon: Icon(Icons.add_shopping_cart),
                label: 'Shopping cart',
                backgroundColor: Colors.red,
              ),
              BottomNavigationBarItem(
                icon: Icon(Icons.favorite),
                label: 'Favorite',
                backgroundColor: Colors.green,
              ),
              BottomNavigationBarItem(
                icon: Icon(Icons.person),
                label: 'Profile',
                backgroundColor: Colors.yellow,
              ),
            ],
        ),
        ),
      ),
    );
  }
}

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return SafeArea(
      child: SingleChildScrollView(
        scrollDirection: Axis.vertical,
        child: Center(
          child: Padding(
            padding: EdgeInsets.all(10.0),
            child: Column(
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              children: [
                //Image.asset('images/image0.jpg'),
                SizedBox(
                  height: 25.0,
                ),
                Row(
                  mainAxisAlignment: MainAxisAlignment.spaceBetween,
                  children: [
                    Text(
                      'New!',
                      textAlign: TextAlign.left,
                      style: TextStyle(
                        fontSize: 25.0,
                        fontWeight: FontWeight.bold,
                      ),
                    ),
                    IconButton(
                      onPressed: () {},
                      icon: Icon(
                        Icons.arrow_forward_outlined,
                      ),
                    ),
                  ],
                ),
                SizedBox(
                  height: 25.0,
                ),
                SizedBox(
                  height: 300.0,
                  width: double.infinity,
                  child: ListViewAPI(),
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

And now, below is a code of ListViewAPI(), here I've added the elements which I tap to the box('favorites_box'): listview_api.dart

import 'package:flutter/material.dart';
import 'package:hive/hive.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';

String? stringResponse;
Map? mapResponse;
Map? dataResponse;
List? listResponse;

class ListViewAPI extends StatefulWidget {
  const ListViewAPI({Key? key}) : super(key: key);

  @override
  _ListViewAPIState createState() => _ListViewAPIState();
}

class _ListViewAPIState extends State<ListViewAPI> {
  Future apiCall() async {
    http.Response response;
    response = await http.get(Uri.parse("https://api.client.macbro.uz/v1/product"));
    if(response.statusCode == 200) {
      setState(() {
        //  stringResponse = response.body;
        mapResponse = jsonDecode(response.body);
        listResponse = mapResponse!['products'];
      });
    }
  }

  @override
  void initState() {
    super.initState();
    apiCall();
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: Scrollbar(
          child: ListView.builder(
            scrollDirection: Axis.horizontal,
            itemBuilder: (context, index) {
              return Stack(
                children: [
                  Card(
                    child: Image.network(
                      listResponse![index]['image'],
                    ),
                  ),
                  Positioned(
                    right: 0,
                    child: InkWell(
                      child: IconButton(
                        onPressed: () async {
                          await Hive.box('favorites_box').put(listResponse![index]['image'], listResponse);
                        },
                        icon: Icon(
                          Icons.favorite_rounded,
                          color: Colors.red,
                        ),
                      ),
                    ),
                  ),
                ],
              );
            },
            itemCount: listResponse == null ? 0 : listResponse!.length,
          ),
        ),
    );
  }
}

So here, I created a list, and tried to save the elements from box named "favorites_box" and got data which was added while I tap favorite IconButton upper but without success( : favorite_page.dart

import 'package:flutter/material.dart';
import 'package:hive/hive.dart';
import 'package:hive_flutter/hive_flutter.dart';

import '../View/gridview_api.dart';

class FavoritePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {

    return Scaffold(
      body: ValueListenableBuilder(
        valueListenable: Hive.box('favorites_box').listenable(),
        builder: (context, box, child) {
         List posts = List.from(Hive.box('favorites_box').values);
          return ListView.builder(
            scrollDirection: Axis.horizontal,
            itemBuilder: (context, index) {
              return Column(
                children: [
                  Text(
                    'List of favorite products'
                  ),
                  Card(
                    child: posts[index] == null ? Text('nothing(') : posts[index],
                   // child: Hive.box('favorites_box').get(listResponse),
                  ),
                ],
              );
            },
          );
        },
      ),
    );
  }
}

I'll be grateful if someone could help me with this problem, as I'm trying to fix this issue for a couple of days P.s. I'm so sorry for some inconveniences, I'm a novice yet that's why hope you'll understand me Thanks!


Solution

  • Alright. I now have a solution. It is a bit more complex than what you started with but it worked during testing.

    Using https://marketplace.visualstudio.com/items?itemName=hirantha.json-to-dart I created a model class from the API data JSON. One for the Product and one for the Price map inside of Product.

    product_model.dart import 'dart:convert';

    import 'package:equatable/equatable.dart';
    import 'package:hive_flutter/hive_flutter.dart';
    
    import 'price.dart';
    
    part 'product_model.g.dart';
    
    @HiveType(typeId: 1)
    class ProductModel extends Equatable {
      @HiveField(0)
      final String? id;
      @HiveField(1)
      final String? name;
      @HiveField(2)
      final String? slug;
      @HiveField(3)
      final bool? active;
      @HiveField(4)
      final String? image;
      @HiveField(5)
      final String? code;
      @HiveField(6)
      final String? order;
      @HiveField(7)
      final int? cheapestPrice;
      @HiveField(8)
      final Price? price;
      @HiveField(9)
      final int? discount;
    
      const ProductModel({
        this.id,
        this.name,
        this.slug,
        this.active,
        this.image,
        this.code,
        this.order,
        this.cheapestPrice,
        this.price,
        this.discount,
      });
    
      factory ProductModel.fromMap(Map<String, dynamic> data) => ProductModel(
            id: data['id'] as String?,
            name: data['name'] as String?,
            slug: data['slug'] as String?,
            active: data['active'] as bool?,
            image: data['image'] as String?,
            code: data['code'] as String?,
            order: data['order'] as String?,
            cheapestPrice: data['cheapest_price'] as int?,
            price: data['price'] == null
                ? null
                : Price.fromMap(data['price'] as Map<String, dynamic>),
            discount: data['discount'] as int?,
          );
    
      Map<String, dynamic> toMap() => {
            'id': id,
            'name': name,
            'slug': slug,
            'active': active,
            'image': image,
            'code': code,
            'order': order,
            'cheapest_price': cheapestPrice,
            'price': price?.toMap(),
            'discount': discount,
          };
    
      /// `dart:convert`
      ///
      /// Parses the string and returns the resulting Json object as [ProductModel].
      factory ProductModel.fromJson(String data) {
        return ProductModel.fromMap(json.decode(data) as Map<String, dynamic>);
      }
    
      /// `dart:convert`
      ///
      /// Converts [ProductModel] to a JSON string.
      String toJson() => json.encode(toMap());
    
      ProductModel copyWith({
        String? id,
        String? name,
        String? slug,
        bool? active,
        String? image,
        String? code,
        String? order,
        int? cheapestPrice,
        Price? price,
        int? discount,
      }) {
        return ProductModel(
          id: id ?? this.id,
          name: name ?? this.name,
          slug: slug ?? this.slug,
          active: active ?? this.active,
          image: image ?? this.image,
          code: code ?? this.code,
          order: order ?? this.order,
          cheapestPrice: cheapestPrice ?? this.cheapestPrice,
          price: price ?? this.price,
          discount: discount ?? this.discount,
        );
      }
    
      @override
      bool get stringify => true;
    
      @override
      List<Object?> get props {
        return [
          id,
          name,
          slug,
          active,
          image,
          code,
          order,
          cheapestPrice,
          price,
          discount,
        ];
      }
    }
    

    price.dart import 'dart:convert';

    import 'package:equatable/equatable.dart';
    import 'package:hive_flutter/hive_flutter.dart';
    
    part 'price.g.dart';
    
    @HiveType(typeId: 2)
    class Price extends Equatable {
      @HiveField(0)
      final int? price;
      @HiveField(1)
      final int? oldPrice;
      @HiveField(2)
      final int? uzsPrice;
      @HiveField(3)
      final int? secondPrice;
      @HiveField(4)
      final int? secondUzsPrice;
    
      const Price({
        this.price,
        this.oldPrice,
        this.uzsPrice,
        this.secondPrice,
        this.secondUzsPrice,
      });
    
      factory Price.fromMap(Map<String, dynamic> data) => Price(
            price: data['price'] as int?,
            oldPrice: data['old_price'] as int?,
            uzsPrice: data['uzs_price'] as int?,
            secondPrice: data['second_price'] as int?,
            secondUzsPrice: data['second_uzs_price'] as int?,
          );
    
      Map<String, dynamic> toMap() => {
            'price': price,
            'old_price': oldPrice,
            'uzs_price': uzsPrice,
            'second_price': secondPrice,
            'second_uzs_price': secondUzsPrice,
          };
    
      /// `dart:convert`
      ///
      /// Parses the string and returns the resulting Json object as [Price].
      factory Price.fromJson(String data) {
        return Price.fromMap(json.decode(data) as Map<String, dynamic>);
      }
    
      /// `dart:convert`
      ///
      /// Converts [Price] to a JSON string.
      String toJson() => json.encode(toMap());
    
      Price copyWith({
        int? price,
        int? oldPrice,
        int? uzsPrice,
        int? secondPrice,
        int? secondUzsPrice,
      }) {
        return Price(
          price: price ?? this.price,
          oldPrice: oldPrice ?? this.oldPrice,
          uzsPrice: uzsPrice ?? this.uzsPrice,
          secondPrice: secondPrice ?? this.secondPrice,
          secondUzsPrice: secondUzsPrice ?? this.secondUzsPrice,
        );
      }
    
      @override
      bool get stringify => true;
    
      @override
      List<Object?> get props {
        return [
          price,
          oldPrice,
          uzsPrice,
          secondPrice,
          secondUzsPrice,
        ];
      }
    }
    

    I then used https://docs.hivedb.dev/#/custom-objects/generate_adapter to create adapters for both of those. You can read the documentation to see how that is done using build_runner and the hive_generator packages.

    In main.dart I registered both of the adapters and opened up a box with the ProductModel type from product_model.dart. main.dart

    import 'package:flutter/material.dart';
    import 'package:get/get.dart';
    import 'package:hive_flutter/hive_flutter.dart';
    import 'package:test/product_model/price.dart';
    import 'package:test/product_model/product_model.dart';
    
    import 'favorite_page.dart';
    import 'homepage.dart';
    
    void main() async {
      // await GetStorage.init();
      await Hive.initFlutter();
      Hive.registerAdapter(PriceAdapter());
    
      Hive.registerAdapter(ProductModelAdapter());
    
      await Hive.openBox<ProductModel>('favorites_box');
      runApp(MainPage());
    }
    
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return GetMaterialApp(
          initialRoute: '/',
          getPages: [
            GetPage(name: '/', page: () => MyApp()),
            GetPage(name: '/main-page', page: () => MainPage()),
            GetPage(name: '/favorite_page', page: () => FavoritePage()),
            // Dynamic route
          ],
          home: MainPage(),
        );
      }
    }
    

    listview_api.dart is mostly the same with the exception of mapping the products from listResponse to ProductModel objects.

    import 'package:flutter/material.dart';
    import 'package:hive/hive.dart';
    import 'package:http/http.dart' as http;
    import 'dart:convert';
    
    import 'package:test/product_model/product_model.dart';
    
    String? stringResponse;
    Map? mapResponse;
    Map? dataResponse;
    List? listResponse;
    
    class ListViewAPI extends StatefulWidget {
      const ListViewAPI({Key? key}) : super(key: key);
    
      @override
      _ListViewAPIState createState() => _ListViewAPIState();
    }
    
    class _ListViewAPIState extends State<ListViewAPI> {
      Future apiCall() async {
        http.Response response;
        response =
            await http.get(Uri.parse("https://api.client.macbro.uz/v1/product"));
        if (response.statusCode == 200) {
          setState(() {
            //  stringResponse = response.body;
            mapResponse = jsonDecode(response.body);
    
            listResponse = mapResponse!['products'];
            listResponse =
                listResponse!.map((e) => ProductModel.fromMap(e)).toList(); // Map all of the products in listResponse to a ProductModel object.
          });
        }
      }
    
      @override
      void initState() {
        super.initState();
        apiCall();
      }
    
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          debugShowCheckedModeBanner: false,
          home: Scrollbar(
            child: ListView.builder(
              scrollDirection: Axis.horizontal,
              itemBuilder: (context, index) {
                return Stack(
                  children: [
                    Card(
                      child: Image.network(
                        listResponse![index].image!,
                      ),
                    ),
                    Positioned(
                      right: 0,
                      child: InkWell(
                        child: IconButton(
                          onPressed: () async {
                            await Hive.box<ProductModel>('favorites_box').put(
                                listResponse![index].image, listResponse![index]);
                          },
                          icon: Icon(
                            Icons.favorite_rounded,
                            color: Colors.red,
                          ),
                        ),
                      ),
                    ),
                  ],
                );
              },
              itemCount: listResponse == null ? 0 : listResponse!.length,
            ),
          ),
        );
      }
    }
    

    homepage.dart is unchanged.

    favorite_page.dart was changed to a stateful widget and then gets the box values on init.

    import 'package:flutter/material.dart';
    import 'package:hive/hive.dart';
    import 'package:hive_flutter/hive_flutter.dart';
    import 'package:test/product_model/product_model.dart';
    
    class FavoritePage extends StatefulWidget {
      @override
      State<FavoritePage> createState() => _FavoritePageState();
    }
    
    class _FavoritePageState extends State<FavoritePage> {
      var posts;
      @override
      void initState() {
        super.initState();
        posts = Hive.box<ProductModel>('favorites_box').values.toList();
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          body: ListView.builder(
            scrollDirection: Axis.horizontal,
            itemBuilder: (context, index) {
              return Stack(
                children: [
                  Card(
                    child: Image.network(
                      posts[index].image!,
                    ),
                  ),
                  Positioned(
                    right: 0,
                    child: InkWell(
                      child: IconButton(
                        onPressed: () async {
                          await Hive.box<ProductModel>('favorites_box')
                              .delete(posts[index]);
                        },
                        icon: Icon(
                          Icons.favorite_rounded,
                          color: Colors.red,
                        ),
                      ),
                    ),
                  ),
                ],
              );
            },
            itemCount: posts == null ? 0 : posts.length,
          ),
        );
      }
    }
    

    I really encourage you to read the documentation on Hive as it contains a wealth of information. Another tip when coding with hive is to make sure you are clearing out the storage and cache for your emulator or physical device regularly. I have had too many headaches dealing with errors in Hive simply because I forgot to clear the storage and cache which was resulting in bad data despite having changed my source code.