Skip to content

🐛 Unexpected focus behavior with NavigationView and ListView #913

@loic-sharma

Description

@loic-sharma

Describe the bug
My app has a ListView with ListTiles and a Delete key shortcut to delete the currently focused ListTile. The app's focus behaves unexpectedly if it has a NavigationView.

Fluent UI version: 4.7.2
Flutter version: 3.10.6 (stable channel, framework revision f468f3366c)

Examples

❌ Fluent app with NavigationView

Code...
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:fluent_ui/fluent_ui.dart' as fluent;

void main() {
  debugFocusChanges = true;
  runApp(const App3());
}

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

  @override
  State<App3> createState() => _App3State();
}

class _App3State extends State<App3> {
  List<String> _items = List.generate(5, (index) => 'Item: $index');

  void _removeItem(String value) {
    setState(() => _items = _items.where((item) => item != value).toList());
  }

  @override
  Widget build(BuildContext context) {
    return fluent.FluentApp(
      title: 'Remove list tile',
      home: fluent.NavigationView(
        pane: fluent.NavigationPane(
          selected: 0,
          items: [
            fluent.PaneItem(
              icon: const Icon(fluent.FluentIcons.mail),
              title: const Text('Home'),
              body: Focus(
                autofocus: true,
                child: ListView.builder(
                  itemCount: _items.length,
                  itemBuilder: (context, index) {
                    final item = _items[index];
                    return CallbackShortcuts(
                      bindings: {   
                        const SingleActivator(LogicalKeyboardKey.delete): () => _removeItem(item),
                      },
                      child: fluent.ListTile(
                        title: Text('Item $item'),
                        onPressed: () {
                          debugPrint('Item $item tapped');
                        },
                      ),
                    );
                  },
                ),
              ),
            ),
          ]
        )
      ),
    );
  }
}

Notice:

  1. Tabbing past the first ListTile loses focus. I have to press Tab to regain the focus.
  2. Deleting a ListTile using the Delete shortcut loses the focus. I have to press Tab to regain the focus.

fluent_list_delete_bug

✅ Fluent app without NavigationView

Code...
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:fluent_ui/fluent_ui.dart' as fluent;

void main() {
  debugFocusChanges = true;
  runApp(const App2());
}

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

  @override
  State<App2> createState() => _App2State();
}

class _App2State extends State<App2> {
  List<String> _items = List.generate(5, (index) => 'Item: $index');

  void _removeItem(String value) {
    setState(() => _items = _items.where((item) => item != value).toList());
  }

  @override
  Widget build(BuildContext context) {
    return fluent.FluentApp(
      home: Container(
        color: Colors.white,
        child: ListView.builder(
          itemCount: _items.length,
          itemBuilder: (context, index) {
            final item = _items[index];
            return CallbackShortcuts(
              bindings: {   
                const SingleActivator(LogicalKeyboardKey.delete): () => _removeItem(item),
              },
              child: fluent.ListTile(
                title: Text(item),
                onPressed: () => debugPrint('Item $item tapped'),
              ),
            );
          },
        ),
      ),
    );
  }
}

This app works as expected (though with some minor focus highlight flashing):

fluent_list_delete_base

✅ Material app

Code...
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

void main() {
  debugFocusChanges = true;
  runApp(const App1());
}

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

  @override
  State<App1> createState() => _App1State();
}

class _App1State extends State<App1> {
  List<String> _items = List.generate(5, (index) => 'Item: $index');

  void _removeItem(String value) {
    setState(() => _items = _items.where((item) => item != value).toList());
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: ListView.builder(
          itemCount: _items.length,
          itemBuilder: (context, index) {
            final item = _items[index];
            return CallbackShortcuts(
              bindings: {   
                const SingleActivator(LogicalKeyboardKey.delete): () => _removeItem(item),
              },
              child: ListTile(
                title: Text('Item $item'),
                onTap: () =>  debugPrint('Item $item tapped'),
              ),
            );
          },
        ),
      ),
    );
  }
}

The app works as expected:

material_list_delete

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions