Skip to content

Octopus router removes route instead of replacing when name duplicates exist in stack #26

@yelmuratoff

Description

@yelmuratoff

Describe the bug
Octopus router automatically deduplicates routes by name when replacing the top route. If a route with the same name already exists in the stack, calling replace() (upsertLast or something like that) removes the existing instance instead of creating a new one.

To Reproduce
Steps to reproduce the behavior:

  1. Navigate to home
  2. Push gallery
  3. Push picture
  4. Call replace('home')
    Observe the result

Expected behavior
The stack should result in: [home, gallery, home]
The new home route should replace picture without affecting the earlier instance of home.

Actual behavior
The resulting stack is: [gallery, home]
Octopus removes the earlier instance of home, which is unexpected.

Flutter Doctor
[✓] Flutter (Channel stable, 3.32.4, on macOS 15.5 24F74 darwin-arm64, locale en-KZ)
[✓] Android toolchain - develop for Android devices (Android SDK version 35.0.0)
[✓] Xcode - develop for iOS and macOS (Xcode 16.4)
[✓] Chrome - develop for the web
[✓] Android Studio (version 2024.1)
[✓] VS Code (version 1.101.1)
[✓] Connected device (3 available)
! Error: Browsing on the local area network for Aidar’s iPhone 15 Pro...
[✓] Network resources

• No issues found!

Additional context
I have potential ideas, but they all seem hardcoded. I would like to ask you, as a package author, if there is a competent solution to this kind of cases? Thanks!

Code:

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

enum Routes with OctopusRoute {
  home('home'),
  gallery('gallery'),
  picture('picture');

  const Routes(this.name);

  @override
  final String name;

  @override
  Widget builder(BuildContext context, OctopusState state, OctopusNode node) =>
      switch (this) {
        Routes.home => const HomeScreen(),
        Routes.gallery => const GalleryScreen(),
        Routes.picture => const PictureScreen(),
      };
}

void main() => runApp(const MyApp());

class MyApp extends StatefulWidget {
  const MyApp({super.key});

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  final router = Octopus(routes: Routes.values, defaultRoute: Routes.home);
  @override
  Widget build(BuildContext context) {
    return MaterialApp.router(
      routerConfig: router.config,
      builder: (context, child) => OctopusTools(
        octopus: router,
        child: child ?? const SizedBox.shrink(),
      ),
    );
  }
}

class HomeScreen extends StatelessWidget {
  const HomeScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Home')),
      body: Center(
        child: Column(
          children: [
            Text(
              'Current stack: ${context.octopus.state.children.map((c) => c.name).toList()}',
            ),
            const SizedBox(height: 20),
            ElevatedButton(
              onPressed: () => context.octopus.push(Routes.gallery),
              child: const Text('Push to Gallery'),
            ),
          ],
        ),
      ),
    );
  }
}

class GalleryScreen extends StatelessWidget {
  const GalleryScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Gallery')),
      body: Center(
        child: Column(
          children: [
            Text(
              'Current stack: ${context.octopus.state.children.map((c) => c.name).toList()}',
            ),
            const SizedBox(height: 20),

            ElevatedButton(
              onPressed: () => context.octopus.push(Routes.picture),
              child: const Text('Push to Picture'),
            ),
          ],
        ),
      ),
    );
  }
}

class PictureScreen extends StatelessWidget {
  const PictureScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Picture')),
      body: Center(
        child: Column(
          children: [
            Text(
              'Current stack: ${context.octopus.state.children.map((c) => c.name).toList()}',
            ),
            const SizedBox(height: 20),
            Text('After replace i want stack: [home, gallery, home]'),
            ElevatedButton(
              onPressed: () {
                context.octopus.upsertLast(Routes.home);
              },
              child: const Text('Replace Picture with Home'),
            ),
          ],
        ),
      ),
    );
  }
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions