dwyl / flutter-navigation-menu-demo

A quick demo of building a navigation menu in Flutter to illustrate the functionality in isolation.
GNU General Public License v2.0
6 stars 1 forks source link

[EPIC] Feat: Dynamic Navigation Menu #4

Closed nelsonic closed 1 year ago

nelsonic commented 1 year ago

To facilitate having an arbitrary number of lists, we need to investigate how to dynamically update the Navigation Menu in a Flutter App.

Requirements / Roadmap:

Todo

The first thing we need to is:

Note: my initial T5m Googling for "flutter navigation drawer dynamic" returned the following:

LuchoTurtle commented 1 year ago

Alright. After I finish the work done in https://github.com/dwyl/calendar/pull/25, I'll pick this up. After having an initial read, although I understand the question, could you clarify how we will handle offline usage of the app? Assuming we are showing lists within the navigation menu, this assumes the user person needs to have an online connection to fetch the lists so they are displayed in the menu.

So if there's no internet, will the user not be able to use the navigation menu? What caching strategy would you recommend? I know this is forward-looking but I'm just hoping to clarify this beforehand.

nelsonic commented 1 year ago

Thanks for confirming there is enough info in here to get going. πŸ‘Œ

For now, we just need to confirm that it's possible for the items in the Flutter Navigation Drawer to updated dynamically based on input by the person using the App. πŸ“±

I'm always glad to see proactive questions. πŸŽ‰ Hopefully this is addressed in: https://github.com/dwyl/mvp/issues/363 In the MVP and eventually the App, the Nav Menu will actually be a specific of list That way we can take advantage of the list re-ordering work you have already done to allow people to customise/personalise their Menu to their needs/tastes. ✨

But again, for now, we just want to confirm that we can dynamically create the Navigation Drawer. πŸ‘Œ The complexity/detail of how the rest will work will be fleshed out in subsequent issues. πŸ’­

LuchoTurtle commented 1 year ago

As I'm reading through this, I need some clarifications on some points because I feel like the original description lacks specificity and there aren't any Figma wireframes to go for:

I've taken a look at https://github.com/nickyrabit/NavigationDrawer-with-Dynamic-Menu-and-Submenu-using-Flutter-Pageview and although it certainly is useful, it's just conditional rendering based on the person's permissions. That is, it just shows and hides specific page routes according to what a user can or cannot do.

What you seem to be asking is to create new pages on the fly (for this we would need a template - but even then, what are these new pages pertaining to? Seems a lot like Notion). But I'm not sure if that's even possible. The only thing remotely close I found was https://stackoverflow.com/questions/63857496/how-to-add-tabs-dynamically-to-tabbar-in-flutter but it just pertains to Tabs, not Navigation Routes of the app.

MaterialApp (which is used at the root of the project) does have a field to handle dynamic routing: https://stackoverflow.com/questions/49065668/dynamic-navigation-routes. In here, we can handle routes even if they are not statically defined...

I know we can pop and push named routes like we do with pushNavigator (like we're currently doing) -> https://stackoverflow.com/questions/71471867/how-to-implement-arguments-to-dynamic-routing. But the problem of what screen/widget should be rendered?

Perhaps I'm overcomplicating things... or outright interpreting what you're saying wrong πŸ˜…

LuchoTurtle commented 1 year ago

Stumbled upon https://stackoverflow.com/questions/75234251/how-to-implement-a-flutter-drawer-menu-submenu-feature-where-menu-submenu-render but it's a dead-end. Just for future notice.

nelsonic commented 1 year ago

@LuchoTurtle thank you for seeking further clarification. πŸŽ‰ (it saves time in the long run to ask as many questions as possible up-front πŸ‘Œ)

You are definitely over-complicating things. We want a Nav Menu that is generated from a .json file dynamically. That's it. Right now that's all we need to test. If that step is possible then we can try the more advanced part. Which, yes, is routing to a specific screen. But we definitely aren't creating "new pages on the fly". The screens will already exist.

Here is a good example of a Dynamic Nav Drawer in a Native iOS App:

image

The question you are trying to answer is: How do they achieve this?

nelsonic commented 1 year ago

In the Gmail Scenario, I created a new label on my Desktop (using the GMail Web interface in Google Chrome) and it immediately appeared in my GMail Native iOS App on my Phone. This is Dyanmic Nav. No re-compilation. No "screens on the fly". Clearly they rendering the LABELS section of the Nav based on an API request. That is what we are trying to figure out right now. πŸ™

LuchoTurtle commented 1 year ago

Thank you for replying. I might be over-complicating things (and definitely not using the best words when asking my questions) but the concept of a menu item seems to be a bit abstract to me of what they exactly pertain to.

Hence why I was wondering about "creating pages on the fly" - what pages are they redirecting to? I'm using page and screen interchangeably. In Flutter I would need to know where I'm navigating into. But, as you've said, that's Level 2.

The example you've shown with Gmail is simply filtering e-mails by the label. I understand what you've said that you want to create new menu items and render them accordingly and be shown on all devices - no recompilation needed. I was just looking into more clarification of what these menu items referred to and their purpose.

Nonetheless, I'm now more certain about what I need to work out. The rest of the requirements (for example, changing text colour) can be fleshed out at a later stage.

Thanks πŸ‘

LuchoTurtle commented 1 year ago

I've been and am currently working on getting nested tiles with TileView working on different levels and will try to create them from a json file, as you've stated.

nelsonic commented 1 year ago

When I edit the label on the GMail Web Interface:

image image

It changes on the Web:

image

But most importantly it's instantly reflected in the iOS App:

ios-gmail-nav-menu-dynamic

Zero lag/latency. It's just there. πŸŽ‰

LuchoTurtle commented 1 year ago

Modals 🀒

nelsonic commented 1 year ago

Yeah, Modals are evil. and they're everywhere. πŸ™„

nelsonic commented 1 year ago

As for where the menu items in the Nav will "link to" they will simply take the person to another screen. For now we don't need to know what that screen is. But for testing purposes you can just create a screen that renders the text of the menu item.

nelsonic commented 1 year ago

@LuchoTurtle your progress shown in https://github.com/dwyl/flutter-navigation-menu-demo/pull/5#issuecomment-1527976549 and on our standup is looking very good. 😍 πŸŽ‰ Keep up the great work and please post regular comments especially when you are stuck with something. πŸ’¬ πŸ™

LuchoTurtle commented 1 year ago

Having a few problems actually persisting updates on reordering indexes with the json file.

The problem I'm having is because the object that the json represents is recursive (I'm aware the depth is max 3 but writing search and update functions with O(n^3) doesn't seem plausible/readable for this case).

I've tried using a few packages:

I implemented recursion to fetch a deeply nested object with a given id, a la:

But I still couldn't get updating items within an item to properly re-order within the same level.

To properly get this working, I'm using recursion to find and update a given object within the tree with the updated children. I'm passing the list of updated menu items before recursively updating them. This is done in https://github.com/dwyl/flutter-navigation-menu-demo/commit/e095cbc5e1ead77f221ae8f5eb20f0f537c01481.

The generated json output is correct. But Im not yet persisting it because I wanted to get the reordering correct. I'll have to use a library to correctly save the menu when the app loads on something akin to "local storage" and then update it accordingly.

nelsonic commented 1 year ago

@LuchoTurtle hmm ... πŸ’­ Let's break this down to figure it out. πŸ’­ Want to pair on this tomorrow morning after standup? πŸ§‘β€πŸ’»

nelsonic commented 1 year ago

@LuchoTurtle as discussed in our in-person standup today, our objective with the i18n in the menu is to stick to the default way it's done in Flutter as much as possible. That means having a separate language file for each new language we want to add to the App. Please see: https://github.com/dwyl/learn-flutter/issues/90 πŸ™

LuchoTurtle commented 1 year ago

Understood. For those that are unaware, I initially thought we wanted to have each label in the menu item coming from the API/JSON file and allow the user to i18nize their own menu items in different languages.

If you want such implementation, you can visit the state of the repo on the commit https://github.com/dwyl/flutter-navigation-menu-demo/tree/d1383a5e1e3271de6f409f844fe34f33cb9f3685.

However, similarly to what happens with Gmail, user labels do not get translated. What we want is:

LuchoTurtle commented 1 year ago

Having a bit of trouble getting the tests to run correctly after making changes with i18n (different from the previous approach). It seems that running each test individually works and all pass. But if I run flutter test --coverage, the second test widget test breaks. I've narrowed down to the line inside app_localization.dart...

  Future loadLanguage() async {
    final path = 'assets/i18n/${_locale.languageCode}.json';
    String jsonStringValues = await rootBundle.loadString(path);   // breaks here

Apparently, there are constraints accessing rootBundle during tests (as seen in https://stackoverflow.com/questions/72015892/how-to-get-result-of-rootbundle-loadstringkey-during-flutter-widget-test).

Adding TestWidgetsFlutterBinding.ensureInitialized(); on top of each test doesn't seem to be working either. πŸ˜•

LuchoTurtle commented 1 year ago

After reading https://stackoverflow.com/questions/60079645/flutter-how-to-mock-a-call-to-rootbundle-loadstring-then-reset-the-mocked, it seems that almost all tests pass...

By setting String jsonStringValues = await rootBundle.loadString(path, cache: false); (rootBundle is not cached), all but one test pass. I'll investigate what's going on with the last one