pezza
pezza

Reputation: 11

Flutter: How to pass a value in to another class from a streamprovider / listview builder

Background:

I am trying to produce a scrollable listview of images. This screen will be a three stage process in that it will:

On each press, the image selected will build in to a new array, ultimately forming another kind of Image View made up of the three chosen images to be shown along the bottom, with a button to click that will then read the "sentence" of the 3 selected images.

What I have at the moment:

I have the initial ListView displaying with the correct filtered images, provided through a StreamProvider which calls a class containing the ListView. Currently this is filtered as I have hard coded the query.

This is the screen that ultimately displays the ListView

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:showmeapp/Screens/Home/image_list.dart';
import 'package:showmeapp/Services/database.dart';
import 'package:showmeapp/models/new_image.dart';

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

    return StreamProvider<List<NewImage>>.value(
      value: FirestoreDatabaseService().firestoreImages,
      child: Scaffold(
        backgroundColor: Colors.blue[300],
        appBar: AppBar(
          title: Text('Make A Sentence'),
          backgroundColor: Colors.blue[700],
          elevation: 0.0,
        ),
        body: ImageList(),
      )
    );
  }
}

This is the ImageList class that is called from that screen

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:showmeapp/Screens/Home/image_card.dart';
import 'package:showmeapp/models/new_image.dart';

class ImageList extends StatefulWidget {
  @override
  _ImageListState createState() => _ImageListState();

}

class _ImageListState extends State<ImageList> {
  @override
  Widget build(BuildContext context) {
    
    final images = Provider.of<List<NewImage>>(context) ?? [];
    final requestImages = images.where((element) => element.imageClass == 'request').toList();
    
    return ListView.builder(
      scrollDirection: Axis.horizontal,
      itemCount: requestImages.length,
      itemBuilder: (context, index) {
        return Container(
          padding: EdgeInsets.symmetric(horizontal: 12.0, vertical: 24.0),
          height: MediaQuery.of(context).size.height * 0.35,
          child: ImageCard(newImage: requestImages[index]),
        );
      },
    );
  }
}

and this is the custom ImageCard class that is called from the ImageList class

import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:showmeapp/Screens/Home/image_list.dart';
import 'package:showmeapp/models/new_image.dart';

class ImageCard extends StatelessWidget {
  final NewImage newImage;

  ImageCard({this.newImage});

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Container(
          width: MediaQuery.of(context).size.width * 0.3,
          height: MediaQuery.of(context).size.height * 0.2,
          child: Card(
            elevation: 30.0,
            shape: RoundedRectangleBorder(
              borderRadius: BorderRadius.circular(5.0),
              side: BorderSide(width: 2.0, color: Colors.blueAccent),
            ),
            child: GestureDetector(
              child: Padding(
                padding: const EdgeInsets.all(8.0),
                child: Image.asset(
                  'assets/${newImage.imageLocation}',
                  scale: 0.8,
                  height: MediaQuery.of(context).size.height * 0.2,
                  width: MediaQuery.of(context).size.width * 0.1,
                ),
              ),
              onTap: () {
                // String optionSelected = newImage.imageClass;

              },
            ),
          ),
        ),
        Container(
          width: MediaQuery.of(context).size.width * 0.3,
          child: Padding(
            padding: EdgeInsets.all(5.0),
            child: Center(
              child: Text(
                newImage.imageDescription,
                style: TextStyle(
                  fontSize: 40.0,

                ),
              ),
            ),
          ),
        )
      ],
    );
  }
}

And finally, just for completeness, this is the Firebase related functions and calls behind all of that

import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:showmeapp/models/new_image.dart';

// A class for the Firestore Database Service

class FirestoreDatabaseService {

  final String iid;
  
  // Constructor for Database Class
  FirestoreDatabaseService({ this.iid });
  
  // create a reference to the Firestore 'images' collection
  final CollectionReference firestoreImageCollection = FirebaseFirestore.instance.collection('images');
  
  // Add or update image data - This will be useful for initial creation of image meta data for the app
  Future updateFirestoreImageData(String description, String location, String type, String category) async {
    return await firestoreImageCollection.doc(iid).set({
      'image_category': category,
      'image_description': description,
      'image_location': location,
      'image_type': type
    });
  }
  
  // new image list from snapshot
  List<NewImage> _newImageFromSnapshot(QuerySnapshot snapshot) {
    return snapshot.docs.map((doc) {
      return NewImage(
        imageClass: doc.data()['image_class'] ?? '',
        imageType: doc.data()['image_type'] ?? '',
        imageCategory: doc.data()['image_category'] ?? '',
        imageLocation: doc.data()['image_location'] ?? '',
        imageDescription: doc.data()['image_description'] ?? ''
      );
    }).toList();
  }

  // Get images from collection via stream
  Stream<List<NewImage>> get firestoreImages {
    return firestoreImageCollection.snapshots()
      .map(_newImageFromSnapshot);
  }
}

What I am struggling with:

As the user selects one of the 'request' images, I then want to pass through a value so that the next set of images displayed is of the type 'object'.

But if I pass this through, it always sends the Provider Of request as Null and returns nothing.

The code that doesn't work:

ImageList class

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:showmeapp/Screens/Home/image_card.dart';
import 'package:showmeapp/models/new_image.dart';

class ImageList extends StatefulWidget {
  @override
  _ImageListState createState() => _ImageListState();

  String searchClass;
  ImageList({ this.searchClass });
}

class _ImageListState extends State<ImageList> {
  @override
  Widget build(BuildContext context) {
    final String searchClass = ImageList().searchClass;
    final images = Provider.of<List<NewImage>>(context) ?? [];
    final requestImages = images.where((element) => element.imageClass == searchClass.toString()).toList();
    print('search class is: $searchClass');
    print('Class search class is: ${ImageList().searchClass}');
    
    return ListView.builder(
      scrollDirection: Axis.horizontal,
      itemCount: requestImages.length,
      itemBuilder: (context, index) {
        return Container(
          padding: EdgeInsets.symmetric(horizontal: 12.0, vertical: 24.0),
          height: MediaQuery.of(context).size.height * 0.35,
          child: ImageCard(newImage: requestImages[index]),
        );
      },
    );
  }
}

Sentence Screen/Widget

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:showmeapp/Screens/Home/image_list.dart';
import 'package:showmeapp/Services/database.dart';
import 'package:showmeapp/models/new_image.dart';

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

    return StreamProvider<List<NewImage>>.value(
      value: FirestoreDatabaseService().firestoreImages,
      child: Scaffold(
        backgroundColor: Colors.blue[300],
        appBar: AppBar(
          title: Text('Make A Sentence'),
          backgroundColor: Colors.blue[700],
          elevation: 0.0,
        ),
        body: ImageList(searchClass: 'request',),
      )
    );
  }
}

So, passing through that string of 'request' and including that in the where clause results in Null. It only works when I hard specify 'request' in the where clause. The above example isn't the action from the selected image, but is the same principal in where I am trying to tell it to initially load the 'request' images via a passed value. Once I can get the initial value through, then I am hoping any further passing will slot in to place :-)

I have tried variations of the variables being in the class, the class and the state (as it is now) but can't seem to get it to carry the value.

How would I achieve this? Is it possible using the StreamProvider / ListView builder?

UPDATE My NewImage class is as follows:

class NewImage {

  final String imageType; // Whether the image is a Category image or Object Image
  final String imageDescription; // Text to display with the image
  final String imageLocation; // The URL or Location of the image
  final String imageCategory; // The category that the image belongs to
  final String imageClass; // A tag to identify the stage for sentences, such as Request, Object, Ending

  NewImage({ this.imageType, this.imageDescription, this.imageLocation, this.imageCategory, this.imageClass });

}

Thanks

Andrew

Upvotes: 1

Views: 1255

Answers (1)

orotype
orotype

Reputation: 459

I am not entirely sure if I understand your question, however I think your problem is caused by this line:

final images = Provider.of<List<NewImage>>(context) ?? [];

I assume your new_image.dart model is something like this:

class NewImage extends ChangeNotifier {
  List<Image> _imageList = [];
  List<Image> get imageList => _imageList;
  void fetchImages() {
    _imageList = fetchFunction();
    notifyListeners();
  }
 ....
}

If this is the case your code should be something like this:

final images = Provider.of<NewImage>(context, listen: false).imageList;

Upvotes: 1

Related Questions