In flutter string text are directly set to the TextField widget like:
new Text('Hello, How are you?')
Is correct way ? or we can maintain all string in one file and use it like:
<string name="name_hint">Hello, How are you?</string>
Is it possible ?
Flutter currently doesn’t have a dedicated resources-like system for strings. At the moment, the best practice is to hold your copy text in a class as static fields and accessing them from there. For example:
class Strings {
static const String welcomeMessage = "Welcome To Flutter";
}
Then in your code, you can access your strings as such:
Text(Strings.welcomeMessage)
source
Edit May '19:
There's now this package that allows you to create json files with your Strings. It will allow you to create Strings for plurals, genders and languages etc
You can create a separate json file for each language like so:
string_en.json
{
"thanks": "Thanks."
}
string_nl.json
{
"thanks": "Dankjewel."
}
And then use this to access it
S.of(context).thanks;
It will know which language to choose based on your phone's default language.
Screenshot:
Full Code (Null safe):
For those of you who don't want to use any 3rd party plugin, here is how you can do it.
Create a folder strings in assets. Put your language file in it.
assets
strings
- en.json // for english
- ru.json // for russian
Now in en.json, write your string, for example.
{
"text1": "Hello",
"text2": "World"
}
Similarly, in ru.json,
{
"text1": "Привет",
"text2": "Мир"
}
Add this to pubspec.yaml file (mind the spaces)
flutter:
uses-material-design: true
assets:
- assets/strings/en.json
- assets/strings/ru.json
flutter_localizations:
sdk: flutter
Now you are all set to use these strings in your app. Here is the sample code, the AppBar shows the translated text.
void main() {
runApp(
MaterialApp(
locale: Locale("ru"), // switch between en and ru to see effect
localizationsDelegates: [const DemoLocalizationsDelegate()],
supportedLocales: [const Locale('en', ''), const Locale('ru', '')],
home: HomePage(),
),
);
}
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(DemoLocalizations.of(context).getText("text2") ?? "Error")),
);
}
}
// this class is used for localizations
class DemoLocalizations {
static DemoLocalizations? of(BuildContext context) {
return Localizations.of<DemoLocalizations>(context, DemoLocalizations);
}
String getText(String key) => language[key];
}
late Map<String, dynamic> language;
class DemoLocalizationsDelegate extends LocalizationsDelegate<DemoLocalizations> {
const DemoLocalizationsDelegate();
#override
bool isSupported(Locale locale) => ['en', 'ru'].contains(locale.languageCode);
#override
Future<DemoLocalizations> load(Locale locale) async {
String string = await rootBundle.loadString("assets/strings/${locale.languageCode}.json");
language = json.decode(string);
return SynchronousFuture<DemoLocalizations>(DemoLocalizations());
}
#override
bool shouldReload(DemoLocalizationsDelegate old) => false;
}
You can use the methods represented in the internationalization sections of the documentation to control both centralized string management and translations (if you need translations)
https://flutter.io/tutorials/internationalization/
It might be overkill for a simple app with only a few strings though.
I would separate these classes into individual files, but just to explain my approach for this question.
I have a base class which has my strings getters. Every language I want to support I have to create a class which extends from this class and override its getters. Thus, whenever I create a string, I have to override each implementation of this base class. It is helpful to avoid forgetting to create some locale specific string.
/// Interface strings
class Strings {
String get hello;
}
/// English strings
class EnglishStrings extends Strings {
#override
String get hello => 'Hello';
}
/// Russian strings
class RussianStrings extends Strings {
#override
String get hello => 'Привет';
}
/// Portuguese strings
class PortugueseStrings extends Strings {
#override
String get hello => 'Olá';
}
After that, in a global scope of your application, you could declare a unique instance of the locale you want to use (using a singleton is a good option).
Just showing a short example of using it:
class Resources {
BuildContext _context;
Resources(this._context);
Strings get strings {
// It could be from the user preferences or even from the current locale
Locale locale = Localizations.localeOf(_context);
switch (locale.languageCode) {
case 'pt':
return PortugueseStrings();
case 'ru':
return RussianStrings();
default:
return EnglishStrings();
}
}
static Resources of(BuildContext context){
return Resources(context);
}
}
And finally, using it in some widget:
Text(Resources.of(context).strings.hello)
Using an extension from BuildContext
You can extend BuildContext to create some particular features and give more power to your application.
This is available from Dart 2.7. See more.
app_context_extension.dart
extension AppContext on BuildContext {
Resources get resources => Resources.from(this);
}
favorites_page.dart
import 'package:flutter/material.dart';
// you have to import it yourself. The auto import does not work in this case
import 'package:myapp/ui/extensions/app_context_extension.dart';
class FavoritesPage extends StatefulWidget {
#override
_FavoritesPageState createState() => _FavoritesPageState();
}
class _FavoritesPageState extends State<FavoritesPage> {
#override
Widget build(BuildContext context) {
return Text(context.resources.strings.hello);
}
}
Using GlobalKey
Along with an extension of BuildContext as shown above, you can also use GlobalKey.
Basically, you could use it when you do not have a context instance.
This last one has a good advantage. You could use strings anywhere in your application. In other words, if you use some pattern like MVC for instance and want to use strings in your controllers, you could easily do it.
You can declare something like this:
application.dart
import 'package:myapp/ui/extensions/app_context_extension.dart';
import 'package:myapp/ui/values/resources.dart';
import 'package:flutter/material.dart';
class Application {
static GlobalKey<NavigatorState> navKey = GlobalKey();
static Resources get resources {
return navKey.currentContext.resources;
}
}
main.dart
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
navigatorKey: Application.navKey,
...
And then:
import 'package:flutter/material.dart';
import 'package:myapp/application/application.dart';
class FavoritesPage extends StatefulWidget {
#override
_FavoritesPageState createState() => _FavoritesPageState();
}
class _FavoritesPageState extends State<FavoritesPage> {
#override
Widget build(BuildContext context) {
return Text(Application.resources.strings.hello);
}
}
Hope it helps!
First of all, create a new strings folder in assets and add your language JSON files.
assets
strings
- en.json
- ar.json
This is your en.json file
{
"title": "Flutter app"
}
And this is your ar.json file
{
"title": "تطبيق Flutter"
}
Then, change your pubspec.yaml file like below.
dependencies:
# your other codes
intl: ^0.17.0
flutter_localizations:
sdk: flutter
# your other codes
flutter:
uses-material-design: true
assets:
- assets/strings/en.json
- assets/strings/ar.json
After that, create AppLocalizations.dart class
import 'dart:convert';
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
class AppLocalizations {
static AppLocalizations of(BuildContext context) {
return Localizations.of<AppLocalizations>(context, AppLocalizations);
}
String getText(String key) => language[key];
}
Map<String, dynamic> language;
class AppLocalizationsDelegate extends LocalizationsDelegate<AppLocalizations> {
const AppLocalizationsDelegate();
#override
bool isSupported(Locale locale) => ['en', 'ar'].contains(locale.languageCode);
#override
Future<AppLocalizations> load(Locale locale) async {
String string = await rootBundle.loadString("assets/strings/${locale.languageCode}.json");
language = json.decode(string);
return SynchronousFuture<AppLocalizations>(AppLocalizations());
}
#override
bool shouldReload(AppLocalizationsDelegate old) => false;
}
Finally in your main.dart file make the below changes
void main() async {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: AppLocalizations.of(context).getText("title"),
locale: Locale("en"),
localizationsDelegates: [const AppLocalizationsDelegate()],
supportedLocales: [const Locale('en', ''), const Locale('ar', '')],
home: HomeScreen(),
);
}
}
create "Strings.dart" file and add the below line==>
class Strings
{
static String welcomeScreen="WelCome Page";
static String loadingMessage="Loading Please Wait...!";
}
And then call the file using the below line using the widget
Text(Strings.loadingMessage)
Make sure that the String.dart file has been imported
I use this method instead of using third party lib. Basically, I create a class that holds those values (string, colors, dimens, etc)
resources.dart
import 'dart:ui';
class ResString{
var data = {
'url' : 'https://facebook.com/',
'welcome' : 'Welcome Home',
};
String get(String key){
return data[key];
}
}
class ResColor{
var data = {
'colorPrimary' : 0xff652A04,
'colorPrimaryDark' : 0xffFFFFFF,
'colorPrimaryLight' : 0xffF6EDDD,
};
Color get(String key){
return Color(data[key]);
}
}
To use it, simply call the get method
main.dart
import 'package:my_app/resources.dart';
...
return Container(
color: ResColor().get('colorPrimary')
);
...
It's not funny to mange the languages at all, Android Studio have a build-in plugin of Transalte the words and let you mange it easy, so you can see in a table the Key of the word, and the result in every language you just add, manually of course. soon in Flutter I hope!
I tried some of the solutions suggested in this post, but the one from #Fakhriddin Abdullaev was the one that worked well for me. But I didn't like the fact that one must always lookup the key of each string in the.json files.
So I created a strings.dart:
import 'package:/AppLocalizations.dart';
class Strings {
static String error = "Error Message";
static AppLocalizations? locator = AppLocalizations();
// ignore: non_constant_identifier_names
static String app_name = locator?.getText("app_name") ?? error;
}
When there is an entry in the .json files like:
{
"app_name" : "My awesome AppName",
}
you just need to call strings. and the code completion will suggest needed string:
But do not forget to initialize Strings.locator with the correct context:
Strings.locator = AppLocalizations.of(context);
That is the correct way. In flutter you don't need .xml or .css files to manage your layout/stuff.
Everything is managed using the dart code. Which makes everything much easier.
Related
how i must write this code in bloc v.8 i don know how i see some searches but im not understand and this is my code for classes they give me error => StateError (Bad state: add(DoFetchEvent) was called without a registered event handler.
Make sure to register a handler via on((event, emit) {...})):
class PostBloc extends Bloc<PostEvent, PostState> {
PostRepository repo;
PostBloc(PostState initialState, this.repo) : super(initialState);
Stream<PostState> mapEventToState(PostEvent event) async* {
if (event is DoFetchEvent) {
yield LoadingState();
try {
var posts = await repo.fetchPosts();
yield FetchSuccess(posts: posts);
} catch (e) {
yield ErrorState(message: e.toString());
}
}
}
}
import 'package:equatable/equatable.dart';
class PostEvent extends Equatable {
#override
List<Object?> get props => [];
}
class DoFetchEvent extends PostEvent {}
class PostState extends Equatable {
#override
List<Object?> get props => [];
}
class InitialState extends PostState {}
class LoadingState extends PostState {}
class FetchSuccess extends PostState {
List<PostModel> posts;
FetchSuccess({required this.posts});
}
class ErrorState extends PostState {
String message;
ErrorState({required this.message});
}
void main() {
runApp(MaterialApp(
home: BlocProvider(
create: (context) => PostBloc(InitialState(), PostRepository()),
child: MyApp(),
),
));
}
You can set your InitialState directly in the super constructor without manually passing it in like so.
PostBloc(this.repo) : super(InitialState()) {
on<DoFetchEvent>(_onDoFetchEvent);
}
Then you no longer pass in any state in the BlocProvider
BlocProvider<PostBloc>(
create: (BuildContext context) => PostBloc(PostRepository()),
...
Then your mapEventToState gets replaced with a method that takes the relevant event, and an Emitter<PostState> as arguments. yield then gets replaced with emit in the method.
Your whole class would look like this.
PostBloc(this.repo) : super(InitialState()) {
on<DoFetchEvent>(_onDoFetchEvent);
}
_onDoFetchEvent(
DoFetchEvent event,
Emitter<PostState> emit,
) async {
emit(LoadingState());
try {
var posts = await repo.fetchPosts();
emit(FetchSuccess(posts: posts));
} catch (e) {
emit(ErrorState(message: e.toString()));
}
}
}
That should do it.
Besides that, you're probably getting linter warnings about must_be_immutable on your state classes because PostState extend Equatable.
So I suggest making all PostState parameters final and adding the props override from Equatable to your state classes.
class ErrorState extends PostState {
final String message;
ErrorState({required this.message});
#override
List<Object?> get props => [message];
}
I'm creating a Flutter application. I'm added a BloC to my project for management state.
I created a list with data. And I want to add item to ListView manually with button 'Add'.
I'm wrote a code:
My Item Cubit
class ItemCubit extends Cubit<List<Item>> {
ItemCubit() : super([]);
void addItem(item){
state.add(item);
emit(state);
}
}
Page of Items with Provider:
class SearchPage extends StatelessWidget {
const SearchPage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
home: BlocProvider(
create: (_) => ItemCubit(),
child: Search(),
),
);
}
}
And I call the BlocBuilder in Stateless Widget like this:
body: BlocBuilder<MarketCubit, List<Market>>(
builder: (context, items) => TabBarView(...))
So when I call my function from state:
Item item = Item(1, 'Item 1');
ElevatedButton(onPressed:(){
context.read<ItemCubit>().addItem(item);
}, child: Text('Add Item')),
The ListView doesn't updating. What's problem?
Thanks a lot!
The problem is that you are just adding the item to the state and re-emitting the same (modified) list. Bloc will essentially check the value of the state object to see if it is a new object when you call emit(), if it is the 'same list' as the last state, it will do nothing. It is confusing because even though you are adding a new value to the list, it is still the same object in a sense, just with a new value.
So there should be an easy fix if you just create a new list instead of modifying the original one and emit that.
For example, this should fix the issue:
emit([...state, newValue])
or
final newList = List.from(state)..add(newValue);
emit(newList);
(I had trouble finding in the bloc documentation where this is explained, but there are a number of issue threads where it is discussed -https://github.com/felangel/bloc/issues/2374)
Essentially, the state should be 'immutable', so if you are going to emit a new state, you should actually create a new instance of whatever you are going to emit. The way I typically use the state is to have a class with final fields for whatever state the cubit is tracking, and a copyWith method that makes it easy to create a new instance with any modified fields:
YourCubitState{
final List stateList;
final otherField;
const YourCubitState({
this.stateList = [],
this.otherField,
});
YourCubitState copyWith({
List? stateList,
otherField,
}) =>
YourCubitState(
stateList: stateList ?? this.stateList,
otherField: otherField ?? this.otherField,
);
}
Maybe that is unnecessary if the state is just a single list, but if you find yourself wanting to add properties to the state, that is a good pattern to follow.
create new variable, add them to the new variable list, update your new variable list item to it, emit the data with the new variable. this is how i got it working.
late List<ServicesExpensesList> newList = <ServicesExpensesList>[];
state.utilityDataList.forEach((element) {
newList.add(element);
});
newList.insert(0, utilityData);
newList.sort((a, b) => b.createdAt.compareTo(a.createdAt));
emit(state.copyWith(
utilityDataList: newList,
status: state.status == BlocStateStatus.success
? BlocStateStatus.updated
: BlocStateStatus.success,
))
ItemCubit class superclass not holding your state. You should manage yourself states. So, what I say, I'm saying create list variable inside ItemCubit class and every call addItem method add item to this list and emit this list.
Your Cubit class should be as below;
abstract class ItemState {
const ItemState();
}
class ItemInitial extends ItemState {
const ItemInitial();
}
class ItemStateUpdatedList extends ItemState {
const ItemStateUpdatedList({required this.list});
final List<Item> list;
}
class ItemCubit extends Cubit<ItemState> {
ItemCubit() : super(const ItemInitial());
final List<Item> myItemList = [];
void addItem(item){
myItemList.add(item);
emit(ItemStateUpdatedList(list: myItemList));
}
}
body: BlocBuilder<ItemCubit, ItemState>(
builder:(context, state) {
if(state is ItemStateUpdatedList){
final list = state.list;
return TabBarView(...);
}
return const SizedBox.shrink();
},
)
Cubit or block is a state change emitter, and since state is a set of attributes and properties then, the key thing here is the ability of Cubit and Bloc to figure out that something has been changed with properties or attributes otherwise will not emit new state.
this is how it works for me:
abstract class ObjectListState extends Equatable {
`final List<String> items;
const ObjectListState(this.items);
}
class ObjectListStateInitial extends ObjectListState {
const ObjectListStateInitail(super.items);
#override
List<Object> get props => [];
}
class ObjectListStateItemAdded extends ObjectListState {
final String item;
final List<String> items;
ObjectListStateItemAdded( this.item, this.items) : super(items){
items.add(item);
}
#override
List<Object> get props => [item,items];
}
I know, many people have asked similar questions regarding this, here. But no solution is working for me, and I'm unable to understand the solution. So please someone help me out. I declared the variable user to be non-null (by adding exclamation mark). But somehow, it is returning null. Please someone help me out by tracking down how am i getting this null.
Here is auth_event.dart file -
(Line number 17 is giving the error (from what i think).)
part of 'auth_bloc.dart';
abstract class AuthEvent extends Equatable {
const AuthEvent();
#override
// TODO: implement stringify
bool? get stringify => true;
#override
List<Object> get props => [];
}
class AuthUserChanged extends AuthEvent {
final auth.User? user; //Initial Value is null
const AuthUserChanged({required this.user}); //Now the value is changed
#override
List<Object> get props => [user!]; //Still, the value being shown is null
//TODO: List<Object> get props => [user??auth.User];
}
class AuthLogoutRequested extends AuthEvent {}
And here is auth_bloc.dart file -
import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:firebase_auth/firebase_auth.dart' as auth;
import 'package:flutter/cupertino.dart';
import 'package:flutter_instagram_clone_final/blocs/blocs.dart';
import 'package:flutter_instagram_clone_final/repositories/auth/auth_repository.dart';
part 'auth_event.dart';
part 'auth_state.dart';
class AuthBloc extends Bloc<AuthEvent, AuthState> {
final AuthRepository? _authRepository;
StreamSubscription<auth.User?>? _userSubscription;
AuthBloc({
#required AuthRepository? authRepository
}) : _authRepository = authRepository, super(AuthState.unknown()){
_userSubscription = _authRepository!.user.listen((user) => add(AuthUserChanged(user: user)));
}
#override
Future<void> close() {
_userSubscription!.cancel();
return super.close();
}
#override
Stream<AuthState> mapEventToState (
AuthEvent event,
) async* {
if(event is AuthUserChanged) {
yield* _mapAuthUserChangedToState(event);
}
else if(event is AuthLogoutRequested) {
await _authRepository!.logOut();
}
}
Stream<AuthState> _mapAuthUserChangedToState(AuthUserChanged event) async*{
yield event.user != null
? AuthState.authenticate(user: event.user!)
: AuthState.unauthenticated();
}
}
If there is a need of more information, please comment it out, I will add that.
You null handling in your event class is weird.
This is your class with proper null handling:
class AuthUserChanged extends AuthEvent {
final auth.User user;
const AuthUserChanged({required this.user});
#override
List<Object> get props => [user];
}
The variable user can never be null, because you marked it with the required keyword in your constructor. Now remove any ! concerning the variable user from the rest of your code.
Your other code looks weird too. Like a conversion tool with too litle knowledge ran over it and did it all wrong. What happened? Maybe you should look at your code line by line and fix conceptual mistakes first, before you worry about the compiler and runtime.
I'm trying to run this app with two enum parameters sent to a screen like this:
import 'package:flutter/material.dart';
import '../widgets/meal_item.dart';
import '../models/meal.dart';
class CategoryMealsScreen extends StatelessWidget {
static const routeName = '/category-meals';
#override
Widget build(BuildContext context) {
final routeArgs =
ModalRoute.of(context).settings.arguments as Map<String, String>;
final categoryTitle = routeArgs['title'];
final categoryId = routeArgs['id'];
final categoryMeals = DUMMY_MEALS.where((meal) {
print('a');
return meal.categories.contains(categoryId);
}).toList();
return Scaffold(
appBar: AppBar(
title: Text(categoryTitle),
),
body: ListView.builder(
itemBuilder: (ctx, index) {
return MealItem(
id: categoryMeals[index].id,
title: categoryMeals[index].title,
imageUrl: categoryMeals[index].imageUrl,
duration: categoryMeals[index].duration,
complexity: categoryMeals[index].complexity,
affordability: categoryMeals[index].affordability,
);
},
itemCount: categoryMeals.length,
),
);
}
}
and this is the Meal.dart file :
import 'package:flutter/foundation.dart';
enum Complexity {
Simple,
Challenging,
Hard,
}
enum Affordability {
Affordable,
Pricey,
Luxurious,
}
class Meal {
final String id;
final List<String> categories;
final String title;
final String imageUrl;
final List<String> ingredients;
final List<String> steps;
final int duration;
final Complexity complexity;
final Affordability affordability;
final bool isGlutenFree;
final bool isLactoseFree;
final bool isVegan;
final bool isVegetarian;
const Meal({
#required this.id,
#required this.categories,
#required this.title,
#required this.imageUrl,
#required this.ingredients,
#required this.steps,
#required this.duration,
#required this.complexity,
#required this.affordability,
#required this.isGlutenFree,
#required this.isLactoseFree,
#required this.isVegan,
#required this.isVegetarian,
});
}
the app is running fine if I comment this two lines of the first code above :
complexity: categoryMeals[index].complexity,
affordability: categoryMeals[index].affordability,
and even after running if I uncomment them and reload it using hot reload it works fine without any problems except if I running the app without commenting them then I'm getting this error :
Launching lib\main.dart on Android SDK built for x86 in debug mode...
Running Gradle task 'assembleDebug'...
lib/screens/category_meals_screen.dart:31:46: Error: The argument type 'Complexity/*1*/' can't be assigned to the parameter type 'Complexity/*2*/'.
- 'Complexity/*1*/' is from 'lib/models/meal.dart'.
- 'Complexity/*2*/' is from 'package:deliMeals/models/meal.dart' ('lib/models/meal.dart').
complexity: categoryMeals[index].complexity,
^
lib/screens/category_meals_screen.dart:32:49: Error: The argument type 'Affordability/*1*/' can't be assigned to the parameter type 'Affordability/*2*/'.
- 'Affordability/*1*/' is from 'lib/models/meal.dart'.
- 'Affordability/*2*/' is from 'package:deliMeals/models/meal.dart' ('lib/models/meal.dart').
affordability: categoryMeals[index].affordability,
^
FAILURE: Build failed with an exception.
the problem is ambiguous but it solved by replacing the import line
import '../dummy_data.dart';
with this one
import 'package:deliMeals/dummy_data.dart';
can anyone explain what's the difference?
hi please try this way ... change your import syntax of this file CategoryMealsScreen
from import '../models/meal.dart' to
package:deliMeals/models/meal.dart
If this is regarding the flutter tutorial, this can be caused by inconsistent includes. Check all your files and make sure all your includes are consistent
See inconsistency below.
import '/models/meal.dart'; (file1)
import 'package:flutter_complete_guide/models/Meal.dart'; (file2)
change this to
import '/models/meal.dart'; (file1)
import '/models/meal.dart'; (file2)
Also make sure there aren't duplicate file names. Last close the project in Visual Code by closing the folder and open it again.
I had the same problem and found out the problem is in dummy_data.dart file and the import './models/Meal.dart'; has problem.
try changing
import './models/Meal.dart'; to
import './models/meal.dart';
with lowercase 'm' and problem will be solved.
I have faced the same problem, then I change the import syntax of every file from import '../models/meal.dart' to package:deliMeals/models/meal.dart , I removed the ../ and wrote the whole path in every file
then I run in Terminal
flutter clean
flutter Pub get
And it worked fine
Using Flutter, a kotlin/swift function can be called by something like:
file.dart:
static const platform = const MethodChannel('my.test.flutterapp/battery');
final int result = await platform.invokeMethod('getBatteryLevel');
file.kt:
private val CHANNEL = "my.test.flutterapp/battery"
MethodChannel(flutterView, CHANNEL).setMethodCallHandler { call, result ->
if (call.method == "getBatteryLevel") {
...
} else {
result.notImplemented()
}
}
Is there something similar to call Kotlin function from standard Dart app, like Dart console app!
https://flutter.io/developing-packages/
Plugin packages: A specialized Dart package which contain an API written in Dart code combined with a platform-specific implementation for Android (using Java or Kotlin), and/or for iOS (using ObjC or Swift). A concrete example is the battery plugin package.
...
flutter create --template=plugin -i swift -a kotlin hello
For the VM the mechanisms available are basic OS operations and native extensions.
By OS operations I mean, you could launch a separate process and interact with it, using files or the network stack. This is probably not as fine grained as you're looking for.
Native extensions allow you to call out to C or C++ code. I don't know enough about kotlin to know if you can easily expose functionality to C/C++. If it's possible, this will give you the tightest integration.
https://www.dartlang.org/articles/dart-vm/native-extensions
You can see this project: Full Sample
In Andriod:
class MainActivity: FlutterActivity() {
private val DATA_CHANNEL = "app.channel.shared.data"
override fun configureFlutterEngine(#NonNull flutterEngine: FlutterEngine) {
GeneratedPluginRegistrant.registerWith(flutterEngine);
MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), DATA_CHANNEL).setMethodCallHandler { call, result ->
if (call.method!!.contentEquals("getSharedText")) {
result.success("Shared Text")
}
}
}
}
In Dart:
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
void main() {
WidgetsFlutterBinding.ensureInitialized();
runApp(MyApp());
}
class MyApp extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return _MyAppState();
}
}
class _MyAppState extends State<MyApp> {
static const platform = const MethodChannel('app.channel.shared.data');
String dataShared = "No Data";
#override
void initState() {
super.initState();
getSharedText();
}
getSharedText() async {
var sharedData = await platform.invokeMethod("getSharedText");
if (sharedData != null) {
setState(() {
dataShared = sharedData;
});
}
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home:Scaffold(body: Center(child: Text(dataShared)))
);
}
}