패키지, 저장소 및 앱 카테고리 검색

패키지 상세v1.1.1

routemaster

웹, 모바일 및 데스크톱용으로 사용하기 쉬운 플러터 라우터입니다. URL 기반 라우팅, 간단한 탭 및 중첩된 경로 탐색을 지원합니다.

AndroidiOSWebmacOSWindowsLinux
30일 다운로드
5.9천
좋아요
712
Pub 점수
140

패키지 설치

flutter pub add routemaster

패키지 개요

공개 정보를 바탕으로 생성한 선택 가이드입니다.

Routemaster는 웹, 모바일, 데스크톱 플랫폼을 아우르는 Flutter 애플리케이션용 가볍고 사용하기 쉬운 라우팅 솔루션입니다. URL 기반 네비게이션을 지원하며 중첩 라우트와 탭 기반 네비게이션도 가능하여 복잡한 라우트 관리를 간소화합니다. 단순성과 개발자 경험을 최우선으로 고려해 설계되었으며, 기존의 Flutter 프로젝트에 원활하게 통합됩니다. 깔끔한 API를 통해 라우트 전환, 동적 매개변수, 딥링크를 직관적으로 처리할 수 있습니다. 반복 코드 없이 구조화된 네비게이션을 필요로 하는 앱에 이상적입니다.

사용 사례

  • 동적 콘텐츠를 갖춘 단일 페이지 애플리케이션
  • 모바일 및 웹에서의 멀티탭 인터페이스
  • 복잡한 UI에서 중첩된 라우트 구조
  • 딥링크 및 URL 동기화
  • 일관된 라우팅이 필요한 크로스 플랫폼 앱
  • 최소 설정으로 간단한 탐색

주요 기능

  • URL 기반 라우팅
  • 중첩 및 탭 루트 지원
  • 최소한의 보일러플레이트
  • 크로스 플랫폼 호환성
  • 기존 Flutter 앱과의 간편한 통합
  • 동적 경로 매개변수

적합한 대상

  • 크로스 플랫폼 플러터 앱을 개발하는 개발자
  • 클린하고 유지보수 가능한 라우팅 로직을 우선시하는 팀
  • 딥링크 또는 SPA 유사 동작이 필요한 프로젝트
  • 복잡한 탐색 계층을 가진 앱
  • 직관적인 라우팅 솔루션을 찾는 초보자

고려 사항

  • Flutter의 네이티브 탐색 모델에 제한됨
  • 고급 사용 사례를 위해 추가 상태 관리가 필요할 수 있습니다
  • 표준 패턴을 초과하는 고도로 커스터마이즈된 라우팅 동작에는 적합하지 않음
  • 경로 가드 또는 인증 리디렉션에 내장된 지원 없음

원본 README

연결된 소스 저장소의 README 원문 스냅샷입니다. 최신 내용은 저장소에서 확인하세요.

Routemaster

Hello! Routemaster is an easy-to-use router for Flutter, which wraps over Flutter's Router API.

Build codecov pub

Features

  • Simple declarative mapping from URLs to pages
  • Easy-to-use API: just Routemaster.of(context).push('/page')
  • Really easy nested navigation support for tabs
  • Multiple route maps: for example one for a logged in user, another for logged out
  • Observers to easily listen to route changes
  • Covered by over 250 unit, widget and integration tests

Here's the entire routing setup needed for an app featuring tabs and pushed routes:

final routes = RouteMap(
  routes: {
    '/': (_) => CupertinoTabPage(
          child: HomePage(),
          paths: ['/feed', '/settings'],
        ),

    '/feed': (_) => MaterialPage(child: FeedPage()),
    '/settings': (_) => MaterialPage(child: SettingsPage()),
    '/feed/profile/:id': (info) => MaterialPage(
      child: ProfilePage(id: info.pathParameters['id'])
    ),
  }
);

void main() {
  runApp(
      MaterialApp.router(
        routerDelegate: RoutemasterDelegate(routesBuilder: (context) => routes),
        routeInformationParser: RoutemasterParser(),
      ),
  );
}

And then to navigate:

Routemaster.of(context).push('/feed/profile/1');

...you can see this in action in this simple app example.

There's also a more advanced example.

I would love any feedback you have! Please create an issue for API feedback.

Documentation

Begin with the quick start below, but also see the API reference, wiki and FAQs.


Quick start API tour

Overview

Routemaster generates pages based on the current path. This is the key concept of its path-base routing. Path structure matters.

It uses the path to decide where a page should be pushed. This means the path needs to match your intended page hierarchy.

For example:

'/tabs': (route) => TabPage(child: HomePage(), paths: ['one', 'two']),

// First tab default page
'/tabs/one': (route) => MaterialPage(child: TabOnePage()),

// Second tab default page
'/tabs/two': (route) => MaterialPage(child: TabTwoPage()),

// Second tab sub-page: will be displayed in the 2nd tab because it
// starts with '/tabs/two'
'/tabs/two/subpage': (route) => MaterialPage(child: TabTwoPage()),

// Not a tab page: will not be displayed in in a tab
// because the path doesn't start with '/tabs/one' or '/tabs/two'
'/tabs/notInATab': (route) => MaterialPage(child: NotTabPage()),

Any child paths that begin with /tabs/one or /tabs/two will be pushed into the correct tab.

When navigating to /tabs/two/subpage, the TabPage will be asked "hey, do you know how to handle this path?" and it'll go "sure! it starts with /tabs/two, so it goes in my second tab".

However, navigating to /tabs/notInATab will not be displayed in a tab, but pushed on top of the tab bar.

TabPage will be all "yeah sorry, no idea what to do with that, doesn't match any of my tab paths" and its parent will be asked to hande it.

Path hierarchy matters, for example changing where dialogs are displayed.

Routing

Basic app routing setup
MaterialApp.router(
  routerDelegate: RoutemasterDelegate(
    routesBuilder: (context) => RouteMap(routes: {
      '/': (routeData) => MaterialPage(child: PageOne()),
      '/two': (routeData) => MaterialPage(child: PageTwo()),
    }),
  ),
  routeInformationParser: RoutemasterParser(),
)
Navigate from within pages
Routemaster.of(context).push('relative-path');
Routemaster.of(context).push('/absolute-path');

Routemaster.of(context).replace('relative-path');
Routemaster.of(context).replace('/absolute-path');
Path parameters
// Path '/products/123' will result in ProductPage(id: '123')
RouteMap(routes: {
  '/products/:id': (route) => MaterialPage(
        child: ProductPage(id: route.pathParameters['id']),
      ),
  '/products/myPage': (route) => MaterialPage(MyPage()),
})

Note that routes without path parameters have priority, so in the above example /products/myPage will show MyPage.

Query parameters
// Path '/search?query=hello' results in SearchPage(query: 'hello')
RouteMap(routes: {
  '/search': (route) => MaterialPage(
        child: SearchPage(query: route.queryParameters['query']),
      ),
})
Get current path info within a widget
RouteData.of(context).path; // Full path: '/product/123?query=param'
RouteData.of(context).pathParameters; // Map: {'id': '123'}
RouteData.of(context).queryParameters; // Map: {'query': 'param'}

Tabs

Setup:

RouteMap(
  routes: {
    '/': (route) => TabPage(
          child: HomePage(),
          paths: ['/feed', '/settings'],
        ),
    '/feed': (route) => MaterialPage(child: FeedPage()),
    '/settings': (route) => MaterialPage(child: SettingsPage()),
  },
)

Main page:

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final tabPage = TabPage.of(context);

    return Scaffold(
      appBar: AppBar(
        bottom: TabBar(
          controller: tabPage.controller,
          tabs: [
            Tab(text: 'Feed'),
            Tab(text: 'Settings'),
          ],
        ),
      ),
      body: TabBarView(
        controller: tabPage.controller,
        children: [
          for (final stack in tabPage.stacks) PageStackNavigator(stack: stack),
        ],
      ),
    );
  }
}

Cupertino tabs

Setup:

RouteMap(
  routes: {
    '/': (route) => CupertinoTabPage(
          child: HomePage(),
          paths: ['/feed', '/settings'],
        ),
    '/feed': (route) => MaterialPage(child: FeedPage()),
    '/settings': (route) => MaterialPage(child: SettingsPage()),
  },
)

Main page:

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final tabState = CupertinoTabPage.of(context);

    return CupertinoTabScaffold(
      controller: tabState.controller,
      tabBuilder: tabState.tabBuilder,
      tabBar: CupertinoTabBar(
        items: [
          BottomNavigationBarItem(
            label: 'Feed',
            icon: Icon(CupertinoIcons.list_bullet),
          ),
          BottomNavigationBarItem(
            label: 'Settings',
            icon: Icon(CupertinoIcons.search),
          ),
        ],
      ),
    );
  }
}

Guarded routes

Show default not found page if validation fails:

'/protected-route': (route) => 
    canUserAccessPage()
      ? MaterialPage(child: ProtectedPage())
      : NotFound()

Redirect to another page if validation fails (changes URL):

'/protected-route': (route) => 
    canUserAccessPage()
      ? MaterialPage(child: ProtectedPage())
      : Redirect('/no-access'),

Show another page if validation fails (doesn't change URL):

'/protected-route': (route) => 
    canUserAccessPage()
      ? MaterialPage(child: ProtectedPage())
      : MaterialPage(child: CustomNoAccessPage())

404 Page

Default page to shown on unknown URL:

RouteMap(
    onUnknownRoute: (route, context) {
        return MaterialPage(child: NotFoundPage());
    },
    routes: {
        '/': (_) => MaterialPage(child: HomePage()),
    },
)

Redirect

Redirect one route to another:

RouteMap(routes: {
    '/one': (routeData) => MaterialPage(child: PageOne()),
    '/two': (routeData) => Redirect('/one'),
})

Redirect all routes to login page, for a logged-out route map:

RouteMap(
  onUnknownRoute: (_) => Redirect('/'),
  routes: {
    '/': (_) => MaterialPage(child: LoginPage()),
  },
)

Passing path parameters from original to the redirect path:

RouteMap(routes: {
    '/user/:id': (routeData) => MaterialPage(child: UserPage(id: id)),
    '/profile/:uid': (routeData) => Redirect('/user/:uid'),
})

Swap routing map

You can swap the entire routing map at runtime.

This is particularly useful for different pages depending on whether the user is logged in:

final loggedOutMap = RouteMap(
  onUnknownRoute: (route, context) => Redirect('/'),
  routes: {
    '/': (_) => MaterialPage(child: LoginPage()),
  },
);

final loggedInMap = RouteMap(
  routes: {
    // Regular app routes
  },
);

MaterialApp.router(
  routerDelegate: RoutemasterDelegate(
    routesBuilder: (context) {
			// This will rebuild when AppState changes
      final appState = Provider.of<AppState>(context);
      return appState.isLoggedIn ? loggedInMap : loggedOutMap;
    },
  ),
  routeInformationParser: RoutemasterParser(),
);

Navigation observers

class MyObserver extends RoutemasterObserver {
	// RoutemasterObserver extends NavigatorObserver and
	// receives all nested Navigator events
  @override
  void didPop(Route route, Route? previousRoute) {
    print('Popped a route');
  }

	// Routemaster-specific observer method
  @override
  void didChangeRoute(RouteData routeData, Page page) {
    print('New route: ${routeData.path}');
  }
}

MaterialApp.router(
  routerDelegate: RoutemasterDelegate(
    observers: [MyObserver()],
    routesBuilder: (_) => routeMap,
  ),
  routeInformationParser: RoutemasterParser(),
);

Navigate without a context

app.dart

final routemaster = RoutemasterDelegate(
  routesBuilder: (context) => routeMap,
);

MaterialApp.router(
  routerDelegate: routemaster,
  routeInformationParser: RoutemasterParser(),
)

my_widget.dart

import 'app.dart';

void onTap() {
  routemaster.push('/blah');
}

Hero animations

Hero animations will work automatically on the top-level navigator (assuming you're using MaterialApp or CupertinoApp).

For any child navigators, you'll need to wrap PageStackNavigator in a HeroControllerScope, like this:

HeroControllerScope(
  controller: MaterialApp.createMaterialHeroController(),
  child: PageStackNavigator(
    stack: pageStack,
  )
)

Design goals

  • Integrated: work with the Flutter Router API, don't try to replace it. Try to have a very Flutter-like API.
  • Usable: design around user scenarios/stories, such as the ones in the Flutter storyboard - see here for examples.
  • Opinionated: don't provide 10 options to achieve a goal, but be flexible for all scenarios.
  • Focused: just navigation, nothing else. For example, no dependency injection.

This project builds on page_router.

카테고리

의존성

의존성 목록 3 개

소스 저장소

이 패키지를 사용하는 프로젝트

순위