Troubles with making a Favorite page with Hive DB Flutter

815 Views Asked by At

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!

2

There are 2 best solutions below

1
On BEST ANSWER

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.

12
On

I don't believe this is a problem with your code. However, I do recommend creating a model class for your data and maybe using a FutureBuilder https://api.flutter.dev/flutter/widgets/FutureBuilder-class.html.

I believe the problem is that you have not updated your AndroidManifest.xml file to allow for internet connectivity.

Try adding:

<uses-permission android:name="android.permission.INTERNET" />

to your android\app\src\main\AndroidManifest.xml, above <application.

Further reading: https://flutter-examples.com/add-permissions-in-androidmanifest-xml-file/

After taking a closer look at your issue, I think I figured out the problem.

Hive requires an init:

void main() async {
  // await GetStorage.init(); // Not sure why this was here but doesn't seem to be needed.
  await Hive.initFlutter();

  await Hive.openBox('favorites_box');
  runApp(MainPage());
}

You were also missing a comma in main_page.dart

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

For your favorites page, I replaced the ValueListenableBuilder with just a ListView.builder:

class FavoritePage extends StatelessWidget {
  List posts = List.from(Hive.box('favorites_box').values);

  @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('favorites_box').delete(posts[index]);
                    },
                    icon: Icon(
                      Icons.favorite_rounded,
                      color: Colors.red,
                    ),
                  ),
                ),
              ),
            ],
          );
        },
        itemCount: posts == null ? 0 : posts.length,
      ),
    );
  }
}

There is still an error when you try to use this that says that posts[index]['image'] type 'String' is not a subtype of type 'int' of 'index' but you can easily fix this by creating a model class and accessing everything with those properties. Using model class in flutter (dart) here is an example of a model class. Instead of using DocumentSnapshot, you can add a toList() or toMap() method.

Hope this helps. It is working on my emulator.enter image description here but I am just printing out the full string instead of using the image in the Card child.

Example Model Class:

import 'dart:convert';

void main() async {
  String data = '{"id":"626694d4f1ce2a0012f0fe1c","name":"JBL Party Box On-The-Go","slug":"jbl-party-box-on-the-go-juqgil2ep8ult","active":true,"image":"https://cdn.macbro.uz/macbro/1fad4f47-51f4-4f12-975b-657d780c98af","code":"","order":"0","cheapest_price":0,"price":{"price":520,"old_price":0,"uzs_price":5994000,"second_price":0,"second_uzs_price":7012500},"discount":0}';
  
  var test = new ProductModel.fromJson(json.decode(data));
  print(test.image);
}
  
class ProductModel {
  String? name;
  String? image;
  
  
  ProductModel.fromJson(Map json) {
    this.name = json['id'];
    this.image = json['image'];
  }
  
}