beamer icon indicating copy to clipboard operation
beamer copied to clipboard

beamToNamed not rebuild widgets

Open lemos1235 opened this issue 2 years ago • 1 comments

I called context.beamToNamed("/books"); in HomeScreen, then context.beamToNamed("/books/${e.id}"); in BooksScreen, finnally context.beamToNamed("/books"); in BookDetailScreen; As the result, why the BooksScreen build method hadn`t been called again!

example:

home_screen.dart

import 'package:beamer/beamer.dart';
import 'package:flutter/material.dart';

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("Home Page"),
      ),
      body: Container(
        alignment: Alignment.center,
        child: Column(children: [
          TextButton(
            onPressed: () => context.beamToNamed("/books"),
            child: Text("Books"),
          )
        ]),
      ),
    );
  }
}

books_screen.dart

import 'package:beamer/beamer.dart';
import 'package:beamer_demo/model/info.dart';
import 'package:flutter/material.dart';

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

  @override
  State<BooksScreen> createState() => _BooksScreenState();
}

class _BooksScreenState extends State<BooksScreen> {


  @override
  Widget build(BuildContext context) {    

    var data = Beamer.of(context).currentBeamLocation.data;
    print(data);
    var dataList = [
      BookInfo(id: "22", title: "dog"),
      BookInfo(id: "33", title: "cat"),
      BookInfo(id: "44", title: "pig"),
    ];

    var books = dataList
        .map((e) => ListTile(
              title: Text(e.title),
              onTap: () {   
                context.beamToNamed("/books/${e.id}");
              },
            ))
        .toList();
    return Scaffold(
      appBar: AppBar(
        title: const Text("Books Screen"),
      ),
      body: Container(
          child: SingleChildScrollView(
        child: Column(children: books),
      )),
    );
  }
}

book_details_screen.dart

import 'package:flutter/material.dart';
import 'package:beamer/beamer.dart';

class BookDetailsScreen extends StatelessWidget {
  const BookDetailsScreen(this.bookId, {Key? key}) : super(key: key);

  final String bookId;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("Book Details Screen"),
      ),
      body: Container(
        child: Column(
          children: [
            Text("book $bookId"),
            TextButton(
                onPressed: () {
                  // context.beamBack(data: "2233");
                  context.beamToNamed("/books", data: "5566",);
                },
                child: Text("Back")),
          ],
        ),
      ),
    );
  }
}

main.dart

import 'package:beamer/beamer.dart';
import 'package:flutter/material.dart';

import 'book_details_screen.dart';
import 'books_screen.dart';
import 'home_screen.dart';

void main() {
  runApp(BookApp());
}

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

  final routerDelegate = BeamerDelegate(
    locationBuilder: RoutesLocationBuilder(
      routes: {
        // Return either Widgets or BeamPages if more customization is needed
        '/': (context, state, data) => HomeScreen(),
        '/books': (context, state, data) {
          return BeamPage(
            key: ValueKey('books'),
            title: 'Books',
            child: BooksScreen(),
            onPopPage: (context,delegate, state, page) {
              print("books");
              return true;
            }
          );
        },
        '/books/:bookId': (context, state, data) {
          // Take the path parameter of interest from BeamState
          final bookId = state.pathParameters['bookId']!;
          // Collect arbitrary data that persists throughout navigation
          return BeamPage(
            key: ValueKey('book-$bookId'),
            title: 'A Book #$bookId',
            // popToNamed: '/',
            // type: BeamPageType.scaleTransition,
            child: BookDetailsScreen(bookId),
          );
        }
      },
    ),
  );

  @override
  Widget build(BuildContext context) {
    return MaterialApp.router(
      routeInformationParser: BeamerParser(),
      routerDelegate: routerDelegate,
      // backButtonDispatcher: BeamerBackButtonDispatcher(
      //   delegate: routerDelegate,
      // ),
    );
  }
}

lemos1235 avatar May 31 '22 06:05 lemos1235

Hi @lemos1235 :wave: Sorry for a late response, I somehow missed that notification.

This is in fact an expected behavior. When you beam from /books/:id to /books, you are essentially doing a pop and the BooksPage is already built underneath so it stays as it were IF its key is the same as it were during the last build. This is described a bit here: https://pub.dev/packages/beamer#page-keys

That being said, there are a few approaches you can consider.

  • Including data in constructing a key for the BeamPage that constinas BooksScreen. So something like ValueKey('books-$data'). This is a very good choice for your example because you really are sending a different data when beaming back to /books.
  • You could try an approach I described a bit in this recent article. It doesn't deal with pops per se, but gives a more general solution for synchronizing data throughout navigation.
  • Have some state management for BooksScreen and override onPopPage on its BeamPage where you can trigger a rebuild of BooksScreen through said state management.

I would suggest you try the first approach for now and then maybe second later. Third is a bit tricky and can get very messy.

Let me know if this helped.

slovnicki avatar Jun 10 '22 08:06 slovnicki