0

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!

Barton
  • 3
  • 3
  • Please trim your code to make it easier to find your problem. Follow these guidelines to create a [minimal reproducible example](https://stackoverflow.com/help/minimal-reproducible-example). – Community May 04 '22 at 19:25

2 Answers2

0

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'];
  }
  
}
TetroRL
  • 119
  • 6
  • I've tried a version about internet permission in android xml but unfortunately it doesn't work. And about future builder, I don't think that it could help, as I've problems with Hive DB - a local DB, and listview and all other stuff are working fine with API, the only problem is that I couldn't correctly add already loaded data from API into Hive DB box and get that data in another screen/class. But anyway thanks a lot for your help, I really appreciate it! – Barton May 04 '22 at 17:43
  • @Barton please see my post edit. Hope this works! – TetroRL May 04 '22 at 19:04
  • To be honest, I didn't get so much the point where you said to create a model class and change using DocumentSnapshot to that one, I don't know where I'm using a DocumentSnapshot and how to replace it in this particular example, may be due to that I'm just demotivated with this problem, I would be very grateful if you help me once more sir! And btw thx for your patience) – Barton May 04 '22 at 19:57
  • You are not using DocumentSnapshot. That was just what they used in the example link I sent. What I mean by a model class is a class that is used to model the data from the API. For example: `class ProductModel { String id; String imageUrl; String name; // add the rest of the properties here // Add constructor }` Then you would map each element of your api result to that class so that you end up with a List. Then in the listview builder you can have post[index].imageUrl or whatever property you want. – TetroRL May 04 '22 at 20:05
  • now I got smth like this: Is it right? class FavoritePage extends StatelessWidget { List posts = List.from(Hive.box('favorites_box').values); void addingModel() { List ? models = ProductModel(imageUrl: posts[0]['image'], name: posts[0]['name']) as List?; } ListView.builder( Card( child: Image.network( posts[index]['image'], ), ), ); } class ProductModel { String? imageUrl; String? name; ProductModel({this.imageUrl, this.name}); } – Barton May 04 '22 at 20:55
  • inside favorite_page file theres 2 classes. The first on is FavoritePage the second one is ProductModel. In ProductModel I've just declared some fields and created a constructor and in FavoritePage I've created a List models = ProductModel(imageUrl: posts[0]['image'], name: posts[0]['name']) as List?; – Barton May 04 '22 at 20:58
  • Check out my latest edit. That is an example of what you want to do but you'll want to do it for all of the properties. You will also need to loop through your initial list from the api result and convert each product item into a ProductModel object. Maybe something like `listResponse.map((prod) => ProductModel.fromJson(prod)).toList()` I would recommend creating a products.dart file to hold the ProductModel class – TetroRL May 04 '22 at 21:26
  • Good day Sir! Its still not working, may be some error with async await? – Barton May 05 '22 at 09:58
  • What error are you getting? – TetroRL May 05 '22 at 10:27
  • I've done class FavoritePage extends StatelessWidget { List posts = List.from(Hive.box('favorites_box').values); var test; List pm = ProductModel(test.image, test.name) as List; @override Widget build(BuildContext context) =>Scaffold( body: ListView.builder(scrollDirection: Axis.horizontal, itemBuilder: (context, index) { test = ProductModel.fromJson(posts[index]); return Stack( children: [ Card( child: Image.network( pm.map((prod) => ProductModel.fromJson(prod)).toList(); ),), – Barton May 05 '22 at 11:50
  • its inside favorite_page.dart – Barton May 05 '22 at 11:50
  • I posted another answer. I tested this and it works. You will need to change the import paths since I used a different directory name but other than that you should be good. – TetroRL May 05 '22 at 13:10
0

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.

TetroRL
  • 119
  • 6
  • Oh man, I'm so thankful that even dunno how to show my gratitude to you and your effort! Thanks a lot, you made my day! I really appreciate the time that you've spend on me! – Barton May 05 '22 at 20:08