Download this source code for
5 USD


Download this source code for
5 USD


Download this source code for
5 USD


Download this source code for
5 USD

aps navigator – app pagination system

a wrapper around navigator 2.0 and router/pages to make their use a easier.

this library is just a wrapper around navigator 2.0 and router/pages api that tries to make their use easier:

basic feature set

what we’ve tried to achieve:

  • simple api
  • easy setup
  • minimal amount of “new classes types” to learn:
    • no need to extend(or implement) anything
  • web support (check the images in the following sections):
    • back/forward buttons
    • dynamic urls
    • static urls
    • recover app state from web history
  • control of route stack:
    • add/remove pages at a specific position
    • add multiples pages at once
    • remove a range of pages at once
  • handles operational system events
  • internal(nested) navigators

what we didn’t try to achieve:

  • to use code generation
    • don’t get me wrong. code generation is a fantastic technique that makes code clear and coding faster – we have great libraries that are reference in the community and use it
    • the thing is: it doesn’t seems natural to me have to use this kind of procedure for something “basic” as navigation
  • to use strongly-typed arguments passing

overview

1 – create the navigator and define the routes:

final navigator = apsnavigator.from(
  routes: {
    '/dynamic_url_example{?tab}': dynamicurlpage.route,  
    '/': ...
  },
);

2 – configure materialapp to use it:

class myapp extends statelesswidget {
  const myapp({key? key}) : super(key: key);

  @override
  widget build(buildcontext context) {
    return materialapp.router(
      routerdelegate: navigator,
      routeinformationparser: navigator.parser,
    );
  }
}

3 – create the widget page (route):

class dynamicurlpage extends statefulwidget {
  final int tabindex;
  const dynamicurlpage({key? key, required this.tabindex}) : super(key: key);

  @override
  _dynamicurlpagestate createstate() => _dynamicurlpagestate();

  // builder function
  static page route(routedata data) {
    final tab = data.values['tab'] == 'books' ? 0 : 1;
    return materialpage(
      key: const valuekey('dynamicurlpage'), // important! always include a key
      child: dynamicurlpage(tabindex: tab),
    );
  }
}
  • you don’t need to use a static function as pagebuilder, but it seems to be a good way to organize things.
  • important: avoid using ‘const‘ keyword at materialpage or dynamicurlpage levels, or pop may not work correctly with web history.
  • important: always include a key.

4 – navigate to it:

 apsnavigator.of(context).push(
    path: '/dynamic_url_example',
    params: {'tab': 'books'},
 );
  • the browser’s address bar will display: /dynamic_url_example?tab=books.
  • the page will be created and put at the top of the route stack.

the following sections describe better the above steps.

usage

1 – creating the navigator and defining the routes:

final navigator = apsnavigator.from(

  // defines the initial route - default is '/':
  initialroute: '/dynamic_url_example', 

  //  defines the initial route params - default is 'const {}':
  initialparams: {'tab': '1'},

  routes: {
    // defines the location: '/static_url_example'
    '/static_url_example': pagebuilder..,

    // defines the location (and queries): '/dynamic_url_example?tab=(tab_value)&other=(other_value)'
    // important: notice that the '?' is used only once 
    '/dynamic_url_example{?tab,other}': pagebuilder..,

    // defines the location (and path variables): '/posts' and '/posts/(post_id_value)'
    '/posts': pagebuilder..,
    '/posts/{post_id}': pagebuilder..,

    // defines the location (with path and query variables): '/path/(id_value)?q1=(q1_value)&q2=(q2_value)'.
    '/path/{id}?{?q1,q2}': pagebuilder..,

    // defines app root - default
    '/': pagebuilder..,
  },
);

routes is just a map between templates and page builders:

  • templates are simple strings with predefined markers to path ({a}) and query({?a,b,c..}) values.
  • page builders are plain functions that return a page and receive a routedata. check the section 3 bellow.

given the configuration above, the app will open at: /dynamic_url_example?tab=1.

2 – configure materialapp:

after creating a navigator, we need to set it up to be used:

  • set it as materialapp.router.routedelegate.
  • remember to also add the materialapp.router.routeinformationparser:

    class myapp extends statelesswidget {
    const myapp({key? key}) : super(key: key);

    @override
    widget build(buildcontext context) {
      return materialapp.router(
        routerdelegate: navigator,
        routeinformationparser: navigator.parser,
      );
    }
    

    }

3 – creating the widget page(route):

when building a page:

  • the library tries to match the address templates with the current address. e.g.:
    • template: /dynamic_url_example/{id}{?tab,other}'
    • address: /dynamic_url_example/10?tab=1&other=abc
  • all paths and queries values are extracted and included in a routedata.data instance. e.g.:
    • {'id': '10', 'tab': '1', 'other': 'abc'}
  • this istance is passed as param to the pagebuilder function – static page route(routedata data)
  • a new page instance is created and included at the route stack – you check that easily using the dev tools.

    class dynamicurlpage extends statefulwidget {
    final int tabindex;
    const dynamicurlpage({key? key, required this.tabindex}) : super(key: key);

    @override
    _dynamicurlpagestate createstate() => _dynamicurlpagestate();
    
    // you don't need to use a static function as builder, 
    // but it seems to be a good way to organize things   
    static page route(routedata data) {
      final tab = data.values['tab'] == 'books' ? 0 : 1;
      return materialpage(
        key: const valuekey('dynamicurlpage'), // important! always include a key
        child: dynamicurlpage(tabindex: tab),
      );
    }
    

    }

4 – navigating to pages:

example link: all navigating examples

4.1 – to navigate to a route with query variables:

  • template: /dynamic_url_example{?tab,other}
  • address: /dynamic_url_example?tab=books&other=abc

    apsnavigator.of(context).push(
    path: ‘/dynamic_url_example’,
    params: {‘tab’: ‘books’, ‘other’: ‘abc’}, // add query values in [params]
    );

4.2 – to navigate to a route with path variables:

  • template: /posts/{post_id}
  • address: /posts/10

    apsnavigator.of(context).push(
    path: ‘/post/10’, // set path values in [path]
    );

4.3 – you can also include params that aren’t used as query variables:

  • template: /static_url_example
  • address: /static_url_example

    apsnavigator.of(context).push(
    path: ‘/static_url_example’,
    params: {‘tab’: ‘books’}, // it’ll be added to [routedata.values[‘tab’]]
    );

details

1. dynamic urls example

example link: dynamic urls example

navigator

when using dynamic urls, changing the app’s state also changes the browser’s url. to do that:

  • include queries in the templates. e.g: /dynamic_url_example{?tab}
  • call updateparams method to update browser’s url:
    final aps = apsnavigator.of(context);
    aps.updateparams(
      params: {'tab': index == 0 ? 'books' : 'authors'},
    );
    
  • the method above will include a new entry on the browser’s history.
  • later, if the user selects such entry, we can recover the previous widget’s state using:
    @override
    void didupdatewidget(dynamicurlpage oldwidget) {
      super.didupdatewidget(oldwidget);
      final values = apsnavigator.of(context).currentconfig.values;
      tabindex = (values['tab'] == 'books') ? 0 : 1;
    }
    

what is important to know:

  • current limitation: any value used at url must be saved as string.
  • don’t forget to include a key on the page created by the pagebuilder to everything works properly.

2. static urls example

example link: static urls example

static_url_example

when using static urls, changing the app’s state doesn’t change the browser’s url, but it’ll generate a new entry on the history. to do that:

  • don’t include queries on route templates. e.g: /static_url_example
  • as we did with dynamic’s url, call updateparams method again:
    final aps = apsnavigator.of(context);
    aps.updateparams(
      params: {'tab': index == 0 ? 'books' : 'authors'},
    );
    
  • then, allow state restoring from browser’s history:
    @override
    void didupdatewidget(dynamicurlpage oldwidget) {
      super.didupdatewidget(oldwidget);
      final values = apsnavigator.of(context).currentconfig.values;
      tabindex = (values['tab'] == 'books') ? 0 : 1;
    }
    

what is important to know:

  • don’t forget to include a key on the page created by the pagebuilder to everything works properly.

3. return data example

example link: return data example

return_data_example

push a new route and wait the result:

  final selectedoption = await apsnavigator.of(context).push(
     path: '/return_data_example',
  );

pop returning the data:

  apsnavigator.of(context).pop('do!');

what is important to know:

  • data will only be returned once.
  • in case of user navigate your app and back again using the browser’s history, the result will be returned at didupdatewidget method as result, instead of await call.
    @override
    void didupdatewidget(homepage oldwidget) {
      super.didupdatewidget(oldwidget);
      final params = apsnavigator.of(context).currentconfig.values;
      result = params['result'] as string;
      if (result != null) _showsnackbar(result!);
    }
    

4. multi push

example link: multi push example

multi_push_example

push a list of the pages at once:

  apsnavigator.of(context).pushall(
    // position: (default is at top)
    list: [
      apspushparam(path: '/multi_push', params: {'number': 1}),
      apspushparam(path: '/multi_push', params: {'number': 2}),
      apspushparam(path: '/multi_push', params: {'number': 3}),
      apspushparam(path: '/multi_push', params: {'number': 4}),
    ],
  );

in the example above apspushparam(path: '/multi_push', params: {'number': 4}), will be the new top.

what is important to know:

  • you don’t necessarily have to add at the top; you can use the position param to add the routes at the middle of route stack.
  • don’t forget to include a key on the page created by the pagebuilder to everything works properly.

5. multi remove

example link: multi remove example
multi_remove_example

remove all the pages you want given a range:

  apsnavigator.of(context).removerange(start: 2, end: 5);

6. internal (nested) navigators

example link: internal navigator example

internal_nav_example-1

class internalnavigator extends statefulwidget {
  final string initialroute;

  const internalnavigator({key? key, required this.initialroute})
      : super(key: key);

  @override
  _internalnavigatorstate createstate() => _internalnavigatorstate();
}

class _internalnavigatorstate extends state<internalnavigator> {
  late apsnavigator childnavigator = apsnavigator.from(
    parentnavigator: navigator,
    initialroute: widget.initialroute,
    initialparams: {'number': 1},
    routes: {
      '/tab1': tab1page.route,
      '/tab2': tab2page.route,
    },
  );

  @override
  void didchangedependencies() {
    super.didchangedependencies();
    childnavigator.interceptbackbutton(context);
  }

  @override
  widget build(buildcontext context) {
    return router(
      routerdelegate: childnavigator,
      backbuttondispatcher: childnavigator.backbuttondispatcher,
    );
  }
}

what is important to know:

  • current limitation: browser’s url won’t update based on internal navigator state

Download this source code for
5 USD


Download this source code for
5 USD


Download this source code for
5 USD


Download this source code for
5 USD

Comments are closed.