Why does my Flutter TextButton onPressed function generate a type assignment mismatch?

540 views Asked by At

I want to launch Google Maps using the maps_launcher package.

When I copy the TextButton code below to an new project in the MaterialApp home section (or strip down my own main.dart), it works fine. (EDIT: It has to be wrapped in a Container or Scaffold to work though, otherwise I get the same type mismatch error.)

But, for some reason unknown to me, I keep getting one of two errors in my desired structure:

Scenario/Error A

When I use this code:

onPressed: MapsLauncher.launchQuery(businesses[index].address)

I get this error:

The argument type 'Future<bool>' can't be assigned to the parameter type 'void Function()?'.

Scenario/Error B

When I use this code:

I get this error:

════════ Exception caught by widgets library ═══════════════════════════════════
The following _TypeError was thrown building:
type 'String' is not a subtype of type 'Widget'

When the exception was thrown, this was the stack
#0      BusinessList.build.<anonymous closure>.<anonymous closure>
#1      SliverChildBuilderDelegate.build
#2      SliverMultiBoxAdaptorElement._build
#3      SliverMultiBoxAdaptorElement.createChild.<anonymous closure>
#4      BuildOwner.buildScope
#5      SliverMultiBoxAdaptorElement.createChild
#6      RenderSliverMultiBoxAdaptor._createOrObtainChild.<anonymous closure>
#7      RenderObject.invokeLayoutCallback.<anonymous closure>
#8      PipelineOwner._enableMutationsToDirtySubtrees
#9      RenderObject.invokeLayoutCallback
#10     RenderSliverMultiBoxAdaptor._createOrObtainChild
#11     RenderSliverMultiBoxAdaptor.addInitialChild
#12     RenderSliverList.performLayout
#13     RenderObject.layout
#14     RenderSliverEdgeInsetsPadding.performLayout
#15     RenderSliverPadding.performLayout
#16     RenderObject.layout
#17     RenderViewportBase.layoutChildSequence
#18     RenderViewport._attemptLayout
#19     RenderViewport.performLayout
#20     RenderObject.layout
#21     RenderProxyBoxMixin.performLayout
#22     RenderObject.layout
#23     RenderProxyBoxMixin.performLayout
#24     RenderObject.layout
#25     RenderProxyBoxMixin.performLayout
#26     RenderObject.layout
#27     RenderProxyBoxMixin.performLayout
#28     RenderObject.layout
#29     RenderProxyBoxMixin.performLayout
#30     RenderObject.layout
#31     RenderProxyBoxMixin.performLayout
#32     RenderObject.layout
#33     RenderProxyBoxMixin.performLayout
#34     RenderObject.layout
#35     RenderProxyBoxMixin.performLayout
#36     RenderCustomPaint.performLayout
#37     RenderObject.layout
#38     RenderProxyBoxMixin.performLayout
#39     RenderObject.layout
#40     MultiChildLayoutDelegate.layoutChild
#41     _ScaffoldLayout.performLayout
#42     MultiChildLayoutDelegate._callPerformLayout
#43     RenderCustomMultiChildLayoutBox.performLayout
#44     RenderObject._layoutWithoutResize
#45     PipelineOwner.flushLayout
#46     RendererBinding.drawFrame
#47     WidgetsBinding.drawFrame
#48     RendererBinding._handlePersistentFrameCallback
#49     SchedulerBinding._invokeFrameCallback
#50     SchedulerBinding.handleDrawFrame
#51     SchedulerBinding._handleDrawFrame
#55     _invoke (dart:ui/hooks.dart:150:10)
#56     PlatformDispatcher._drawFrame (dart:ui/platform_dispatcher.dart:270:5)
#57     _drawFrame (dart:ui/hooks.dart:114:31)
(elided 3 frames from dart:async)

Full code

Here's the code where the error above is generated (package imports removed during pasting):

class BusinessList extends StatelessWidget {
  const BusinessList({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Business List')),
      body: StreamBuilder(
        stream: FirebaseFirestore.instance
            .collection('businesses')
            .snapshots()
            .map((snapshot) => snapshot.docs
                .map((doc) => Business.fromJson(doc.data()))
                .toList()),
        builder: (BuildContext context, AsyncSnapshot<List> snapshot) {
          if (snapshot.connectionState == ConnectionState.waiting) {
            return const CircularProgressIndicator();
          }
          if (snapshot.hasData) {
            if (snapshot.data!.isNotEmpty) {
              final businesses = snapshot.data;
              return ListView.builder(
                itemCount: businesses!.length,
                itemBuilder: (BuildContext context, int index) {
                  return Card(
                    child: Column(
                      children: [
                        ExpansionTile(
                          leading: const Icon(Icons.business),
                          title: Text(businesses[index].name),
                          subtitle: Text(businesses[index].category),
                          children: [
                            ListTile(
                              leading: const Icon(Icons.location_pin),
                              title: TextButton(
                                child: businesses[index].address,
                                onPressed: () => MapsLauncher.launchQuery(
                                    businesses[index].address),
                              ),
                            ),
                          ],
                        ),
                      ],
                    ),
                  );
                },
              );
            }
          }
          return const Text('error');
        },
      ),
    );
  }
}

The red-underlined section of my code (line 42) is:

MapsLauncher.launchQuery(businesses[index].address)
2

There are 2 answers

0
Michel On BEST ANSWER

Well, I'm glad yet disappointed to report that it was a beginner's mistake generating that error.

The problem wasn't actually with the onPressed, but with the child that required a Widget wrapping a String.

This was a String:

child: businesses[index].address

So all I had to do was wrap it as such:

child: Text(businesses[index].address)

It's so frustrating when the error seems like it's somewhere else than where it actually is. I hope this helps someone someday...

1
Jared Anderton On

I think it's launchQuery returns a bool, which is appears to the compiler, like you are trying to give a bool to an argument that wants a void Function?

I think this would fix it.

onPressed: () => MapsLauncher.launchQuery(businesses[index].address),

Or you might need to do this one, depending on how the launchQuery works:

onPressed: () async => await MapsLauncher.launchQuery(businesses[index].address),

This addresses the type mismatch, because adding the () => tells the compiler that you are giving it a function for onPressed to execute, when the button in pressed.

Disclaimer: this is untested code