{"id":32303649,"url":"https://github.com/yuewen/mix_stack","last_synced_at":"2026-02-22T00:02:44.392Z","repository":{"id":41281953,"uuid":"331214996","full_name":"yuewen/mix_stack","owner":"yuewen","description":"MixStack lets you connects Flutter smoothly with Native pages, supports things like Multiple Tab Embeded Flutter View, Dynamic tab changing, and more. You can enjoy a smooth transition from legacy native code to Flutter with it.","archived":false,"fork":false,"pushed_at":"2022-06-30T09:48:27.000Z","size":773,"stargazers_count":88,"open_issues_count":0,"forks_count":11,"subscribers_count":4,"default_branch":"master","last_synced_at":"2023-08-20T22:33:21.400Z","etag":null,"topics":["android","flutter","ios"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/yuewen.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2021-01-20T06:33:19.000Z","updated_at":"2023-07-07T08:11:50.000Z","dependencies_parsed_at":"2022-07-07T12:02:13.958Z","dependency_job_id":null,"html_url":"https://github.com/yuewen/mix_stack","commit_stats":null,"previous_names":[],"tags_count":9,"template":null,"template_full_name":null,"purl":"pkg:github/yuewen/mix_stack","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yuewen%2Fmix_stack","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yuewen%2Fmix_stack/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yuewen%2Fmix_stack/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yuewen%2Fmix_stack/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/yuewen","download_url":"https://codeload.github.com/yuewen/mix_stack/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yuewen%2Fmix_stack/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29699338,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-21T23:35:04.139Z","status":"ssl_error","status_checked_at":"2026-02-21T23:35:03.832Z","response_time":107,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["android","flutter","ios"],"created_at":"2025-10-23T06:04:34.794Z","updated_at":"2026-02-22T00:02:44.386Z","avatar_url":"https://github.com/yuewen.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"![alt text](.images/logo.png \"MixStack Logo\")\n\n[中文 README](README_cn.md)\n\n# MixStack\n\nMixStack lets you connects Flutter smoothly with Native pages, supports things like Multiple Tab Embeded Flutter View, Dynamic tab changing, and more. You can enjoy a smooth transition from legacy native code to Flutter with it.\n\n## MixStack basic structure\n\n![alt text](.images/mixstack_structure.png \"MixStack strucure\")\n\nAs above picture shows, each Native Flutter Container provided by MixStack, contains an independent Flutter Navigator to maintain page management, through Stack Widget inside Flutter to let Flutter render the current Native Flutter Container belonged Flutter page stack. With this approach, Flutter and Native's page navigation, and all kind of View interaction became possible and manageable.\n\n## Get Start\n\nHybrid development may sometimes a bit overwhelming and bit complicated, please have some patience.\n\n### Installation\n\nAdd mixstack dependency in pubspec.yaml\n\n```yaml\ndependencies:\n  mix_stack: \u003clastest_version\u003e\n```\n\nRun ```flutter pub get``` in your project folder\n\nRun ```pod install``` in your iOS folder\n\nAdd in Android's `build.gradle`:\n\n```gradle\nimplementation rootProject.findProject(\":mix_stack\")\n```\n\n## How to Integrate\n\n### On Flutter Side\n\nFind your root Widget inside your Flutter project, and add MixStackApp in your initial Widget's build, pass your route generation function to MixStackApp, like below:\n\n```dart\nclass MyApp extends StatelessWidget {\n  void defineRoutes(Router router) {\n    router.define('/test', handler: Handler(handlerFunc: (ctx, params) =\u003e TestPage()));\n  }\n\n  @override\n  Widget build(BuildContext context) {\n    defineRoutes(router);\n    return MixStackApp(routeBuilder: (context, path) {\n      return router.matchRoute(context, path).route; //传回 route\n    });\n  }\n}\n```\n\nFlutter part is done。For detail usage please check [Flutter side usage in detail](#flutter_usage)\n\n### On iOS Side\n\nAfter FlutterEngine excute Run，set engine to MXStackExchange\n\n```objc\n//AppDelegate.m\n[flutterEngine run];\n[MXStackExchange shared].engine = flutterEngine;\n```\n\niOS part is done。For detail usage please check [iOS side usage in detail](#ios_usage)\n\n### On Android Side\n\nMake sure in Application's `onCreate()` execute:\n\n```java\nMXStackService.init(application);\n```\n\nAndroid part is done。For detail usage please check [Android side usage in detail](#android_usage)\n\n## Flutter Side Usage\n\n### Listen to container's Navigator\n\nIf you need to listen to navigator inside container, you can add additional observer builder in MixStackApp initialization.\n\n```dart\nMixStackApp(\n      routeBuilder: (context, path) {\n        return router.matchRoute(context, path).route;\n      },\n      observersBuilder: () {\n        return [CustomObserver()];\n      },\n    )\n```\n\n### Control native UI display\n\nWhen you have native view mixed with your Flutter container, sometimes you may want to hide those native views such like you push a new page inside Flutter, something like picture shows:\n![alt text](.images/native_replacer_problem.png \"Native Replacer Target Problem\")\n\nYou can do so by using `NativeOverlayReplacer`.\n\nWhen you need to achieve this, you just need to wrap your page's root widget with NativeOverlayReplacer, and fill in the Native Overlay view's name that you registered in native.\n\n```dart\n@override\nWidget build(BuildContext context) {\n  return NativeOverlayReplacer(child:Container(), autoHidesOverlayNames:[MXOverlayNameTabBar, 'NativeOverlay1', 'NativeOverlay2']);\n}\n```\n\nWe also offer a simple interface to hide MixStack offered Native Tab to simplify the common needs that you embedded a Fluter tab and that tab have its own navigation stack, and when pushed, you need to hide native tab bar.\n\n```dart\n@override\nWidget build(BuildContext context) {\n  return NativeOverlayReplacer.autoHidesTabBar(child:Container());\n}\n```\n\nIf you want fine tuned control, you can tweak the `persist` attribute\n\n```dart\nList\u003cString\u003e names = await MixStack.getOverlayNames(context); //Get current native overlay names\nnames = names.where((element) =\u003e element.contains('tabBar')).toList();\nNativeOverlayReplacer.of(context).registerAutoPushHiding(names, persist: false); //If we hope this register to work once for single push action, we set persist false, if we want it to work everytime we push a page, set it to true\n```\n\nAfter above code setting, everytime a page pushing action will trigger setting of native UI components, also offer a native UI components snapshot inside Flutter to offer smooth animation and let user ignore the hybrid structure.\n\n### Direct pop the current Flutter container\n\n```dart\nMixStack.popNative(context);\n```\n\n### Force adjust current Flutter container's back gesture status\n\nMixStack handle this very well all the time, but sometimes you may needs this capability, make sure you don't shoot your foot.\n\n```dart\nMixStack.enableNativePanGensture(context, true);\n```\n\n### Flutter respond to current container event\n\nSometimes you may need Flutter code to respond to specific native call, and this achieves that.\n\n```dart\n//Some Widget code\nvoid initState() {\n  super.initState();\n  //Root page register a navigator popToRoot action\n  if (!Navigator.of(context).canPop()) {\n    PageContainer.of(context).addListener('popToTab', (query) {\n      Navigator.of(context).popUntil((route) {\n        return route.isFirst;\n      });\n    });\n  }\n}\n```\n\n### Manually adjust Native Overlay\n\n```dart\nNativeOverlayReplacer.of(ctx)\n                .configOverlays([NativeOverlayConfig(name: MXOverlayNameTabBar, hidden: false, alpha: 1)]);\n```\n\n#### Advance: Subsribe to Flutter App lifecycle\n\nMixStack offers whole Flutter App lifecycle for listen, due to some structure difference than original Flutter lifecycle, if you need to do visibility check or other stuff, consider using this.\n\n```dart\n    MixStack.lifecycleNotifier.addListener(() {\n      print('Lifecycle:${MixStack.lifecycleNotifier.value}');\n    });\n```\n\n#### Advance: Subscribe to current container's SafeAreaInsets change\n\nIf Flutter side UI components wants to know Native container insets change, you can do the follows:\n\n```dart\n  PageContainerInfo containerInfo;\n  @override\n  void initState() {\n    super.initState();\n    //Get current container info, and subscribe changes\n    containerInfo = PageContainer.of(context).info;\n    bottomInset = containerInfo.insets.bottom;\n    containerInfo.addListener(updateBottomInset);\n  }\n\n  @override\n  void dispose() {\n    //Cancel subscription\n    containerInfo.removeListener(updateBottomInset);\n    super.dispose();\n  }\n\n  updateBottomInset() {\n    bottomInset = PageContainer.of(context).info.insets.bottom;\n  }\n```\n\nBeware that this subscription only passing SafeAreaInsets change, if you want to know more about Native Container, use `MixStack.overlayInfos` to get infos.\n\n#### Advance: Get current container's native overlay attributes for more customization\n\n```dart\n  double bottomInset = 0;\n  @override\n  Widget build(BuildContext context) {\n    //Get native overlay infos to adjust UI inset\n    MixStack.overlayInfos(context, [MXOverlayNameTabBar], delay: Duration(milliseconds: 400)).then((value) {\n      if (value.keys.length == 0) {\n        return;\n      }\n      final info = value[MXOverlayNameTabBar];\n      double newInset = info.hidden == false ? info.rect.height : 0;\n      if (bottomInset != newInset) {\n        setState(() {\n          bottomInset = newInset;\n        });\n      }\n    });\n    return Stack(\n          children: [\n            Positioned(\n              bottom: bottomInset + 10,\n              right: 20,\n              child: FloatingActionButton(\n                onPressed: () {\n                  print(\"Floating button follow insets move\");\n                },\n              ),\n            ),\n          ],\n        );\n  }\n```\n\n## iOS side usage\n\n### Put multiple Flutter page in TabBarController\n\nMixStack offsers `MXAbstractTabBarController` for subclass，mainly for adjusting tab insets and handle tabbar visibility.If you wants more, you can implement one yourself.\n\nWhen you want to add one or more Flutter Views, please use `MXContainerViewController` as Tab's child viewController. If somehow your `MXContainerViewController` was gonna embedded inside another VC, please mark the root VC with our custom tag\n\n```objc\nvc.containsFlutter = YES;\n```\n\nThe example code of adding Flutter Pages into Tab shows like below:\n\n```objc\nTabViewController *tabs = [[TabViewController alloc] init];\nUITabBarItem *item1 = [[UITabBarItem alloc] initWithTitle:@\"Demo1\" image:[UIImage imageNamed:@\"icon1\"] tag:0];\nUITabBarItem *item2 = [[UITabBarItem alloc] initWithTitle:@\"Demo2\" image:[UIImage imageNamed:@\"icon2\"] tag:0];\nMXContainerViewController *flutterVC1 = [[MXContainerViewController alloc] initWithRoute:@\"/test\" barItem:item1];\nMXContainerViewController *flutterVC2 = [[MXContainerViewController alloc] initWithRoute:@\"/test_2\" barItem:item2];\nSomeNativeVC *nativeVC = [[SomeNativeVC alloc] init]; //Of course you can add normal native VC ^_^\n[tabs setViewControllers:@[flutterVC1, flutterVC2, nativeVC]];\n```\n\n### Flutter Container as normal VC\n\nMixStack's `MXContainerViewController` can use in normal scene.Just passing the Flutter route matching the target page.\n\n```objc\nMXContainerViewController *flutterVC = [[MXContainerViewController alloc] initWithRoute:@\"/test\"]\n[self.navigationController pushViewController:flutterVC animated:YES];\n```\n\n### Pop Flutter Container\n\nAssume current page is Flutter Container，and you're not sure about whether you need to pop this VC or you need to pop one of the page contains in the container, you can try like below:\n\n```objc\n[[MXStackExchange shared] popPage:^(BOOL popSuccess) {\n  if (!popSuccess) {\n    [self.navigationController popViewControllerAnimated:YES];\n  }\n}];\n```\n\nIf result is true, means inside Flutter Container's navigation stack there's a page being popped, and there's still pages there. If result is false, then it means that current Container contains navigation stack only have root page left, so you can pop the whole container safely.\n\n### Submit event to Flutter Container's Flutter stack\n\n```objc\nMXContainerViewController *flutterVC = (MXContainerViewController *)self.tab.viewControllers.lastObject;\n[flutterVC sendEvent:@\"popToTab\" query:@{ @\"hello\" : @\"World\" }];\n```\n\n### Get current Flutter Container's navigation history\n\nIf returns nil, that means current Container contains zero page.\n\n```objc\nMXContainerViewController *flutterVC = ...;\n[flutterVC currentHistory:^(NSArray\u003cNSString *\u003e *_Nullable history) {\n    NSLog(@\"%@\", history);\n}];\n```\n\n### Lock current container engine rendering\n\nSometimes we need to show some UI above Flutter Container, like some popup window.Due to Flutter rendering mechanism, when you do that, Flutter View will black out. So we offer a snapshot mechanism, when you set `showSnapshot` to true, the whole view will be snapshoted and freeze.When you done your business, you can set it back to false.\n\n```objc\nMXContainerViewController *fc = ...\nfc.showSnapshot = YES;\n```\n\n## iOS advance usage \u0026 Q\u0026A\n\n### Potential problems in multiple Window usage\n\nIn some circumstance, iOS side may use multiple window way to manage UI, it may happened that two window both contains Flutter Container, and it is knowned that different window's VC won't receive callback for visibility. When you meets this situation, you can use codes like below to manually guide MixStack to put FlutterEngine's viewController back to the business correct one.\n\n```objc\n[MXStackExchange shared].engineViewControllerMissing = ^id\u003cMXViewControllerProtocol\u003e _Nonnull(NSString *_Nonnull previousRootRoute) {\n  return someFlutterVCFitsYourBusiness;\n};\n```\n\n### Custom support for NativeOverlayReplacer\n\nMake sure the  Flutter Container VC implement [MXOverlayHandlerProtocol](ios/Classes/MXOverlayHandlerProtocol.h#L21) 。\nYou can check related example code in [MXAbstractTabBarController](ios/Classes/MXAbstractTabBarController.m#L81) 。\n\n### What is ignoreSafeareaInsetsConfig\n\nIn MixStack's MXOverlayHandlerProtocol, there's one method called `ignoreSafeareaInsetsConfig`, this method based on a fact that, **in most circumstance, MixStack suggest to set overlay causing SafeAreaInsets to zero**，that is to say, ，Flutter rendering layer should know nothing about native UI's SafeAreaInsets change, the reason for this suggestion is that SafeAreaInsets changing can cause Flutter re-render everything, for complex UI, this is costy and may cause weired bug. So we suggest in specific Container you implement `ignoreSafeareaInsetsConfig`, then inside Flutter use `MixStack.of(context).overlayInfos` to get the overlay changes info for UI adjustment.\n\n## Android Usage\n### MXFlutterActivity usage\n\nWe offer `MXFlutterActivity` for direct use, just passing target page route registered in Flutter\n\n```java\nIntent intent = new Intent(getActivity(), MXFlutterActivity.class);\nintent.putExtra(ROUTE, \"/test_blue\"); //Passing the targeted page route registered in Flutter\nstartActivity(intent);\n```\n\n### MXFlutterFragment usage inside activity and Tab usage\n\nWe offer `MXFlutterFragment` for fragment usage, it's like `MXFlutterActivity`, also need passing target page route registered in Flutter\n\n```java\nMXFlutterFragment flutterFragment = new MXFlutterFragment();\nBundle bundle = new Bundle();\nbundle.putString(MXFlutterFragment.ROUTE, \"/test_blue\");\nhxFlutterFragment.setArguments(bundle);\n```\n\nFor Tab switching，we need controls over `MXFlutterFragment`，for `MXFlutterFragment` 's  host Activity , you need to implement `IMXPageManager` interface, it's only one function, `getPageManager()`, mainly for getting `MXPageManager` in Activity, this serves two purpose:\n\n- **Controls MXFlutterFragment page lifecycle**\n\n- **Offer Flutter page's native UI control capability**\n\n#### Controls MXFlutterFragment page lifecycle\n\nClassic situation: theres's multiple Tab in activity, each point to different Fragment, you need `IMXPageManager`to control which Fragment to display and also set the `MXFlutterFragment` lifecycle right, as below:\n\n```java\nprivate void showFragment(Fragment fg) {\n    FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();\n    if (currentFragment != fg) {\n        transaction.hide(currentFragment);\n    }\n    if (!fg.isAdded()) {\n        transaction.add(R.id.fl_main_container, fg, fg.getClass().getName());\n    } else {\n        transaction.show(fg);\n    }\n    transaction.commit();\n    currentFragment = fg;\n  \n    pageManager.setCurrentShowPage(currentFragment); //tell  MixStack which MXFlutterFragment to show\n}\n```\n\nSince Flutter have different mechanism with Android, we need to override back logic. So in `MXFlutterFragment`'s host Activity we need to add following:\n\n```java\n@Override\npublic void onBackPressed() {\n    if (pageManager.checkIsFlutterCanPop()) {\n        pageManager.onBackPressed(this);\n    } else {\n        super.onBackPressed();\n    }\n}\n```\n\nWhen Activity gets destroy, we need to also notify MixStack through `IMXPageManager`:\n\n```java\n@Override\nprotected void onDestroy() {\n    super.onDestroy();\n    pageManager.onDestroy();\n}\n```\n\n#### Managing Flutter Container's native UI\n\nWe also use PageManager to achieve this, you can directly intialize one if you don't need to manage native UI, otherwise you need override methods, there's four methods needs to override:\n\n- overlayViewsForNames  get the mapping between view and names\n- configOverlay  Config how native overlay view display, aniate, there's default animation\n- overlayNames get avaiable names\n- overlayView  get overlay views through name\n\nthe example are shown below\n\n```java\nMXPageManager pageManager = new MXPageManager() {\n  @Override\n  public List\u003cString\u003e overlayNames() {\n    List\u003cString\u003e overlayNames = new ArrayList\u003c\u003e();\n    overlayNames.add(\"tabBar\");\n    return overlayNames;\n  }\n\n  @Override\n  public View overlayView(String viewName) {\n    if (\"tabBar\".equals(viewName)) {\n      return mBottomBar;\n    }\n    return null;\n  }\n};\n```\n\n### Submit events to FlutterFragment/FlutterActivity\n\n```java\nflutterFragment.sendEvent(\"popToTab\", query);\nflutterActivity.sendEvent(\"popToTab\", query);\n```\n\n### Get Flutter Container's navigation stack history\n\n```java\npageManager.getPageHistory(new PageHistoryListener() {\n    @Override\n    public void pageHistory(List\u003cString\u003e history) {\n    }\n});\n```\n\n#### Destroy\n\nDestory when your MainActivity `onDestroy` gets called\n\n```java\nMXStackService.getInstance().destroy();\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fyuewen%2Fmix_stack","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fyuewen%2Fmix_stack","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fyuewen%2Fmix_stack/lists"}