Best Practice for Flutter Cloud Code?

I have this listview builder in flutter where I need to get the data for it from cloudcode but I don’t know whether to pass is through a FutureBuilder or using an async class. Their is also an issue with having FutureBuilder inside of the Expanded widget and then nesting the ListView inside. This is on parse_server_sdk_flutter: ^3.0.0. I’m trying to do this the right way instead of using some weird workaround and I couldn’t find a definitive answer on the parse flutter github.
Here is the cloudcode portion

Parse.Cloud.define("getTix", async (request) => {

    const query = new Parse.Query("Ticket");

    query.equalTo("home", request.params.home);

    const results = await query.find({ useMasterKey: true });

    let ticket = [];

    for (let i = 0; i < results.length; i++) {

        ticket = results[i];

    }

    return ticket;

});

Here is my flutter file for this portion


class WalletPage extends StatefulWidget {
  @override
  _WalletPageState createState() => _WalletPageState();
}

class _WalletPageState extends State<WalletPage> {
  bool closeTopContainer = false;
  double topContainer = 0;

  List<Widget> itemsData = [];
  List<dynamic> testList = [];
 
  void getPostsData() {

    List<dynamic> responseList = seatData;
    
    List tixList = await getTickets();

    List<Widget> listItems = [];

    responseList.forEach((post) {
      listItems.add(
        GestureDetector(
          dragStartBehavior: DragStartBehavior.start,
          onTap: () => _openPage((_) => TicketPage()),
          onHorizontalDragStart: (DragStartDetails details) =>
              _openPage((_) => SellingPage()),
          child: Container(
            height: 150,
            margin: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
            decoration: BoxDecoration(
                borderRadius: BorderRadius.all(Radius.circular(20.0)),
                color: Colors.white,
                boxShadow: [
                  BoxShadow(
                      color: Colors.black.withAlpha(100), blurRadius: 10.0),
                ]),
            child: Padding(
              padding:
                  const EdgeInsets.symmetric(horizontal: 20.0, vertical: 10),
              child: Row(
                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                children: <Widget>[
                  Expanded(
                    child: Column(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: <Widget>[
                        Text(
                          post["event"],
                          style: const TextStyle(
                              fontSize: 24, fontWeight: FontWeight.bold),
                        ),
                        Text(
                          post["section"],
                          style:
                              const TextStyle(fontSize: 17, color: Colors.grey),
                        ),
                        Text(
                          post["row"],
                          style:
                              const TextStyle(fontSize: 17, color: Colors.grey),
                        ),
                        Text(
                          post["seat"],
                          style:
                              const TextStyle(fontSize: 17, color: Colors.grey),
                        ),
                      ],
                    ),
                  ),
                  Image.asset(
                    "assets/images/${post["image"]}",
                    height: 100,
                    width: 100,
                  )
                ],
              ),
            ),
          ),
        ),
      );
    });
    setState(() {
      itemsData = listItems;
    });
  }

  Future<List<Ticket>> getTickets() async {
    final ParseCloudFunction function = ParseCloudFunction('getTix');
    final Map<String, dynamic> params = <String, dynamic>{'home': 'Cubs'};
    final response = await function.execute(parameters: params);
    List<Ticket> ticketList =
        response.results!.map((request) => (request as Ticket)).toList();
    return ticketList;
  }

  @override
  void initState() {
    super.initState();
    getPostsData();
    getTickets();
  }

  _openPage(WidgetBuilder pageToDisplayBuilder) {
    Navigator.push(
        context,
        platformPageRoute(
          context: context,
          builder: pageToDisplayBuilder,
        ));
  }

  @override
  Widget build(BuildContext context) {
    final Size size = MediaQuery.of(context).size;
    //final double categoryHeight = size.height * 0.30;
    return SafeArea(
      child: PlatformScaffold(
        backgroundColor: Colors.grey[900],
        body: Container(
          height: size.height,
          child: Column(
            children: <Widget>[
              Row(
                children: <Widget>[
                  Text(
                    "  Events",
                    style: TextStyle(
                      color: Colors.yellow,
                      fontSize: 48,
                      fontWeight: FontWeight.w700,
                      fontFamily: "SF UI Display",
                    ),
                    textAlign: TextAlign.start,
                  ),
                ],
              ),
              const SizedBox(
                height: 10,
              ),
              Expanded(
                child: 
                  FutureBuilder(
                    builder: (context, ticketSnap) {
                      if (ticketSnap.connectionState == ConnectionState.none && ticketSnap.hasData) {
                         return Container();
                      }
                      return ListView.builder(
                      itemCount: 10,
                      physics: BouncingScrollPhysics(),
                      itemBuilder: (context, index) {
                        double scale = 1.0;
                        return Opacity(
                          opacity: scale,
                          child: Align(
                              heightFactor: 0.7,
                              alignment: Alignment.topCenter,
                              child: ticketSnap.data![index]
                          ),
                        );
                      },
                      );
                    },
                  future: getTickets(),
                  ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

Calling a function or a ParseQuery is a Future in Flutter.

So that you don’t have to use a FutureBuilder, call the function in initState.

See this example:

class CheckBoxGroupWidget extends StatefulWidget {
  final Function(List<ParseObject>) onChanged;
  final RegistrationType registrationType;
  final bool multipleSelection;

  const CheckBoxGroupWidget(
      {this.registrationType, this.onChanged, this.multipleSelection = false});

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

class _CheckBoxGroupWidgetState extends State<CheckBoxGroupWidget> {
  List<ParseObject> selectedItems = [];
  List<ParseObject> items = [];

  bool isLoading = true;

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

    doListRegistration(widget.registrationType).then((value) {
      if (value != null) {
        setState(() {
          items = value;
          isLoading = false;
        });
      }
    });
  }

  Widget build(BuildContext context) {
    if (isLoading) {
      return Center(
        child: Container(
            width: 100, height: 100, child: CircularProgressIndicator()),
      );
    }

    return ListView.builder(
        physics: const NeverScrollableScrollPhysics(),
        shrinkWrap: true,
        padding: EdgeInsets.only(top: 8.0),
        itemCount: items.length,
        itemBuilder: (context, index) {
          return checkBoxTile(items[index]);
        });
  }

  Future<List<ParseObject>> doListRegistration(
      RegistrationType registrationType) async {
    QueryBuilder<ParseObject> queryRegistration =
        QueryBuilder<ParseObject>(ParseObject(registrationType.className))
          ..orderByAscending('name');
    final ParseResponse apiResponse = await queryRegistration.query();

    if (apiResponse.success && apiResponse.results != null) {
      items.addAll(apiResponse.results.map((e) => e as ParseObject));
      return apiResponse.results;
    } else {
      return [];
    }
  }

  Widget checkBoxTile(ParseObject data) {
    return CheckboxListTile(
      title: Text(data.get<String>('name')),
      value: selectedItems.contains(data),
      onChanged: (value) {
        if (value) {
          setState(() {
            if (!widget.multipleSelection) {
              selectedItems.clear();
            }
            selectedItems.add(data);
            widget.onChanged(selectedItems);
          });
        } else {
          setState(() {
            selectedItems.remove(data);
            widget.onChanged(selectedItems);
          });
        }
      },
    );
  }
}

Ok first of all thanks I appreciate your response. The biggest issue I face with your code and have faced when I try multiple different solutions is the functions either return a future or don’t return a future when it needs to be a future. This is the current error code I’m getting from apiResponse.result :

A value of type 'List<dynamic>?' can't be returned from the method 'doListRegistration' because it has a return type of 'Future<List<ParseObject>>'.

Can you post your code?
What are you trying to do?
Did you understand the concept of Future on Flutter?

What I’m trying to do is convert this current code I have from using a constants.dart list responseList to be able to have it pull that data from back4app instead. I want to pull this data using cloud code because in the future I need it to only pull the data that matches the current user on the app and I need this to be secure. Currently it adds a new item in the list for each entry on that constants.dart list. I want to do that exact same thing but with the parse data. You can see all the code for the walletPage listed above. Also I am working on changing the image part to pull from another api and that part doesn’t need to be included in this code. I think I understand the Future part of Flutter but I am a beginner so I’m always trying to learn more about Flutter. Here is the constants.dart file:

var seatData = [
  {
    "event": "Cubs Vs. Mets",
    "section": "Section 100",
    "row": "Row 10",
    "seat": "Seat 10C",
    "image": "matchuplogos/cubsvsmets.jpg"
  },
];

also here is the current list view builder setup that works.


  @override
  Widget build(BuildContext context) {
    final Size size = MediaQuery.of(context).size;
    //final double categoryHeight = size.height * 0.30;
    return SafeArea(
      child: PlatformScaffold(
        backgroundColor: Colors.grey[900],
        body: Container(
          height: size.height,
          child: Column(
            children: <Widget>[
              Row(
                children: <Widget>[
                  Text(
                    "  Events",
                    style: TextStyle(
                      color: Colors.yellow,
                      fontSize: 48,
                      fontWeight: FontWeight.w700,
                      fontFamily: "SF UI Display",
                    ),
                    textAlign: TextAlign.start,
                  ),
                ],
              ),
              const SizedBox(
                height: 10,
              ),
              Expanded(
                child: ListView.builder(
                    itemCount: itemsData.length,
                    physics: BouncingScrollPhysics(),
                    itemBuilder: (context, index) {
                      double scale = 1.0;
                      return Opacity(
                        opacity: scale,
                        child: Align(
                            heightFactor: 0.7,
                            alignment: Alignment.topCenter,
                            child: itemsData[index]),
                      );
                    }),
              ),
            ],
          ),
        ),
      ),
    );
  }

Also just realized I forgot the model

import 'dart:core';

import 'package:parse_server_sdk/parse_server_sdk.dart';

class Ticket extends ParseObject {
  Ticket() : super("Ticket");
  Ticket.clone() : this();

  @override
  clone(Map<String, dynamic> map) => Ticket.clone()..fromJson(map);

  String? get currentEvent => get<String?>("event");
  set event(String? event) => set<String?>("event", event);

  String? get currentSection => get<String?>("section");
  set section(String? section) => set<String?>("section", section);

  String? get currentRow => get<String?>("row");
  set row(String? row) => set<String?>("row", row);

  String? get currentSeat => get<String?>("seat");
  set seat(String? seat) => set<String?>("seat", seat);
}

I think I’m getting somewhere with this now. So when I call this function :

  Future<void> getTickets() async {
    final ParseCloudFunction function = ParseCloudFunction('getTix');
    final Map<String, dynamic> params = <String, dynamic>{'home': 'Cubs'};
    final response = await function.execute(parameters: params);
    List<Ticket> ticketList =
        response.results!.map((request) => (request as Ticket)).toList();
    print(ticketList);
  }

List ticketList =
response.results!.map((request) => (request as Ticket)).toList();

This line is causing an issue and saying it is an unhandled exception: Null operator used on a null value. Does this mean their is an issue with the cloud code? I’m trying to split this up into it’s most basic steps to find the root of the problem.

Your cloud code doesn’t seem to be returning any results.

Before taking the result of the Cloud Function, check if it was successful and if the results are different from null.

 Future<void> getTickets() async {
    final ParseCloudFunction function = ParseCloudFunction('getTix');
    final Map<String, dynamic> params = <String, dynamic>{'home': 'Cubs'};
    final response = await function.execute(parameters: params);
    if (response.success && response.results != null) {
    List<Ticket> ticketList =
        response.results!.map((request) => (request as Ticket)).toList();
        print(ticketList);
    }
  }

Cloud Code, try it like this:

Parse.Cloud.define("getTix", async (request) => {

    const query = new Parse.Query("Ticket");

    query.equalTo("home", request.params.home);

    const results = await query.find({ useMasterKey: true });

    let ticket = [];

    for (let i = 0; i < results.length; i++) {

        ticket.push(results[i]);

    }

    return ticket;

});

or

Parse.Cloud.define("getTix", async (request) => {

    const query = new Parse.Query("Ticket");

    query.equalTo("home", request.params.home);

    return await query.find({ useMasterKey: true });
});

I think now it may be an issue with mapping out the results and checking if they are null because the logs on back4app show a response is happening for the cloudcode but the flutter is returning null which is odd. I’ve tried both of these functions but both return null. One thing I thought of is maybe the flutter code is getting confused that I don’t want to map everything from the cloud response to the model in flutter. I am still using the same model as I was before.

  Future<List<Ticket>> getTix() async {
    final ParseCloudFunction function = ParseCloudFunction('getTix');
    final Map<String, dynamic> params = <String, dynamic>{'home': 'Cubs'};
    final response = await function.execute(parameters: params);
    if (response.success && response.results != null) {
      List<Ticket> ticketList =
          response.results!.map((request) => (request as Ticket)).toList();
      return ticketList;
    } else {
      throw "darn";
    }
  }

  Future<void> getTickets() async {
    final ParseCloudFunction function = ParseCloudFunction('getTix');
    final Map<String, dynamic> params = <String, dynamic>{'home': 'Cubs'};
    final response = await function.execute(parameters: params);
    if (response.success && response.results != null) {
      print(response.results);
    } else {
      print("darn");
    }
  }

So everything is now working except I can’t seem to set that list value in the initState. It will give me the result I want when I use the list inside the initstate but I get a null value if I call it in my void getPostsData. Here is the code

  Future<List> getTickets() async {
    final ParseCloudFunction function = ParseCloudFunction('getTix');
    final Map<String, String> params = <String, String>{'home': 'Cubs'};
    var response = await function.execute(parameters: params);
    print(response.statusCode);
    if (response.success && response.result != null) {
      print("Works");
      return response.result;
    } else {
      return [];
    }
  }

    getTickets().then((value) {
      setState(() {
        tixlist = value;
        //print(tixlist[0]["event"]);
        isLoading = false;
        print("setstate is complete");
      });
    });
    if (isLoading != true) {
      getPostsData();
      print("DONEEEEEEEEEEE");
    }

JUST DO THIS FOR ANYONE READING

  void initState() {
    super.initState();
    getTickets().then((value) {
      setState(() {
        tixlist = value;
        isLoading = false;
        print("setstate is complete");
        if (isLoading != true) {
          getPostsData();
          print("DONEEEEEEEEEEE");
        }
      });
    });



  Future<List> getTickets() async {
    final ParseCloudFunction function = ParseCloudFunction('getTix');
    final Map<String, String> params = <String, String>{'home': 'Cubs'};
    var response = await function.execute(parameters: params);
    print(response.statusCode);
    if (response.success && response.result != null) {
      print("WORKY TIMEEEEEE");
      return response.result;
    } else {
      return [];
    }

  }