Problem with using go_router and Listview.builder together

108 views Asked by At

I am trying to build a dictionary app. My homepage, which is returned from the main file, is supposed to have the words. I want to implement go router in the home screen which is called DictionaryScreenEnglish, and by tapping the words I want go router to push to the entry pages. I am unable to implement the onTap functionality when the words are tapped. Below is the code of my DictionaryScreenEnglish

import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:zeetionary/dictionary/english_dictionary/alphabet/letter_a/letters_ab/en_entry_aback.dart'; // a page that contains the entry for the word "aback"

final GoRouter _router = GoRouter(
  routes: <RouteBase>[
    GoRoute(
      path: '/dictionaryenglish',
      builder: (BuildContext context, GoRouterState state) {
        return const DictionaryScreenEnglish();
      },
      routes: <RouteBase>[
        GoRoute(
          path: 'english/:aback',
          builder: (BuildContext context, GoRouterState state) {
            return EnglishEntryAback();
          },
        ),
      ],
    ),
  ],
);

class DictionaryScreenEnglish extends StatefulWidget {
  const DictionaryScreenEnglish({Key? key}) : super(key: key);

  @override
  State<DictionaryScreenEnglish> createState() =>
      _DictionaryScreenEnglishState();
}

class _DictionaryScreenEnglishState extends State<DictionaryScreenEnglish> {
  final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>();
  final List<String> allWordsEnglish = [
    "aback",
    "abacus",
  ];
  List<String> filteredWords = [];
  final TextEditingController _searchController = TextEditingController();

  @override
  void initState() {
    super.initState();
    filteredWords = List.from(allWordsEnglish);
  }

  void filterWords(String query) {
    setState(() {
      filteredWords = allWordsEnglish
          .where((word) => word.toLowerCase().contains(query.toLowerCase()))
          .toList();
    });
  }

  void clearSearch() {
    _searchController.clear();
    filterWords('');
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      key: scaffoldKey,
      body: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        crossAxisAlignment: CrossAxisAlignment.center,
        children: [
          Directionality(
            textDirection: TextDirection.ltr,
            child: Padding(
              padding: const EdgeInsets.all(16.0),
              child: SizedBox(
                height: 60,
                child: TextField(
                  controller: _searchController,
                  onChanged: filterWords,
                  decoration: InputDecoration(
                    labelText: 'Search here',
                    prefixIcon: const Icon(Icons.search),
                    suffixIcon: IconButton(
                      icon: const Icon(Icons.clear),
                      onPressed: clearSearch,
                    ),
                    border: const OutlineInputBorder(),
                  ),
                ),
              ),
            ),
          ),
          Expanded(
            child: Directionality(
              textDirection: TextDirection.ltr,
              child: EnglishDictionary(
                words: filteredWords,
                onTapWord: (wordsEnglish) {
                  if (wordsEnglish == "aback") {
                    context.go('english/:aback');
                  }
                },
              ),
            ),
          ),
        ],
      ),
    );
  }

  @override
  void dispose() {
    _searchController.dispose();
    super.dispose();
  }
}

class EnglishDictionary extends StatelessWidget {
  final List<String> words;
  final Function(String) onTapWord;

  const EnglishDictionary({
    Key? key,
    required this.words,
    required this.onTapWord,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return ListView.builder(
      itemCount: words.length,
      itemBuilder: (BuildContext context, int index) {
        return ListTileEnglish(
          wordsEnglish: words[index],
          onTap: () {
            onTapWord(words[index]);
          },
        );
      },
    );
  }
}

class ListTileEnglish extends StatelessWidget {
  final String wordsEnglish;
  final VoidCallback? onTap;

  const ListTileEnglish({
    Key? key,
    required this.wordsEnglish,
    this.onTap,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return InkWell(
      onTap: onTap,
      child: ListTile(
        key: key,
        title: Text(wordsEnglish),
        trailing: const Icon(Icons.arrow_forward),
      ),
    );
  }
}

class CardButton extends StatelessWidget {
  final String label;
  final VoidCallback? onPressed;

  const CardButton({
    Key? key,
    required this.label,
    this.onPressed,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return SizedBox(
      height: 50,
      child: Card(
        child: InkWell(
          onTap: onPressed,
          child: Center(
            child: Text(
              label,
              style: const TextStyle(fontSize: 16),
            ),
          ),
        ),
      ),
    );
  }
}
2

There are 2 answers

0
barmacki On

I think maybe the onTap is not being triggered to begin with. Try updating the ListTileEnglish's and CarButton's onTap` handler invocation to something like:

class ListTileEnglish extends StatelessWidget {
...

  @override
  Widget build(BuildContext context) {
    return InkWell(
      ----> onTap: () => onTap(), <----
      child: ListTile(
        key: key,
        title: Text(wordsEnglish),
        trailing: const Icon(Icons.arrow_forward),
      ),
    );
  }
0
Mike On

One possible issue is that you haven't configured your MaterialApp.router().

void main() {
  runApp(MaterialApp.router(
    routerConfig: _router
  ));
}

The other issue is that you have a nested navigation and you want to navigate to english/:aback, which is not a path in your goRouter. If you want to navigate to the correct path with go() you need to specify the whole path to it like this:

onTapWord: (wordEnglish) {
   if (wordEnglish == "aback") {
      context.go('/dictionaryenglish/english/$wordEnglish');
   }
},

Change your GoRouter to accept the pathParameter:

GoRoute(
      path: 'english/:aback',
      builder: (BuildContext context, GoRouterState state) {
        final word = state.pathParameters['aback'] as String;
        if (word == 'aback') {
          // return your widget
        }
      },
    ),