How to export and use global variable - flutter

I am creating a Flutter App and I need a global http client with some setup.
Flutter: 1.5.4
Dart: 2.3.2
// utils/http.dart
import 'dart:io';
import 'package:cookie_jar/cookie_jar.dart';
import 'package:dio/dio.dart';
import 'package:path_provider/path_provider.dart';
Dio http = Dio();
void main() async {
// Save cookies to cookie jar.
Directory appDir = await getApplicationDocumentsDirectory();
http.interceptors.add(CookieManager(PersistCookieJar(dir: appDir.path)));
}
// main.dart
import 'package:flutter/material.dart';
import 'app.dart';
import 'utils/http.dart';
void main() {
print(http.interceptors); // returns []
runApp(App());
}
I expect that main function in http should be automatically executed.

A common solution to this is to wrap it in a Widget and place that widget in the Widget Tree. High up in the tree, so that it's sort of global. It's called "lifing state up".
The out of box solution of Flutter for such things is InheritedWidget. It's worth looking at and understand because most 3rd-party solutions rely on it.
To make life a tad easier, however, I use pacakge:provider. Code would look like so:
Creating
Provider<Dio>(
builder: (_) => Dio(),
child: MaterialApp(/*...*/)
);
Consuming
later in the widget tree:
Consumer<Dio>(
builder: (ctx, dio, _) {
// widget builder method
debugPrint('Dio instance: ${dio}');
return Container();
}
);

Related

Server controlled Maintenance Screen in Flutter (Firebase as Backend)

So i have a question... is it possible, that I can implement a maintenance screen to my app when I need it?
So like, that the App is checking status from the server and when the Value is at maintenance for example, the App loads to the maintenance screen. And when the Value is changed, the App is starting into the normal main screen.
Idk if i can do this with remote config...
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:provider/provider.dart';
import 'package:showcaseview/showcaseview.dart';
import '../providers/user-provider.dart';
import '../screens/auth_screen.dart';
import '../screens/startscreen.dart';
class AutoLoginHandler extends StatefulWidget {
#override
State<AutoLoginHandler> createState() => _AutoLoginHandlerState();
}
class _AutoLoginHandlerState extends State<AutoLoginHandler> {
#override
Widget build(BuildContext context) {
UserProvider up = context.read<UserProvider>();
return StreamBuilder<User?>(
//Streambuilder looks if data is avaliable
stream: FirebaseAuth.instance.authStateChanges(),
builder: (context, snapshot) {
if (snapshot.hasData && snapshot.data != null) {
up.setUser(snapshot.data);
return ShowCaseWidget(
builder: Builder(builder: (context) => MainPage()),
); //when data here goto Startscreen
}
return LoginScreen(); //when no data is here goto Login
},
);
}
}
The code attached is the Code that checks, if the User is registered.
I was able to achieve this using GetX. I store a boolean in the DB and have a Hasura subscription on it. As soon as it changes, the following controller gets called and the routing is being done.
class MaintenanceController extends GetxController {
RxBool isMaintenance = false.obs;
#override
onInit() {
super.onInit();
ever(
isMaintenance,
(_) => {
if (isMaintenance.value) {Get.offNamed(Routes.maintenance)} else {Get.offNamed(Routes.splash)}
});
}
}
The ever() method gets called whenever the value of the first parameter changes, and the second parameter gets executed. The method onInit() gets called upon the initialization of the controller. In my case, when I do Get.lazyPut(() => MaintenanceController()); By doing this, I am able to force the app to move between the maintenance screen and the splash screen - where I do all the initialization (without forcing a restart which might not be accepted by Apple).

Flutter Unit Class with Initializer

I'm trying to write the unit test for this class, but I'm having problems about the initializer... When I instantiate the class in the test file, the caller throws an error that " Null check operator used on a null value". I know that's because the UserProvider is not initialized on the test folders. But how can I mock this??
class ContactController extends ChangeNotifier {
BuildContext context;
ContactController(this.context) {
initializeData();
}
late Contact contact;
initializeData() {
var userProvider = context.read<UserProvider>();
var currentContact = userProvider?.contact;
if (currentContact != null) {
newContact = currentContact;
}
notifyListeners();
}
}
The code below should do the trick. The thing is that you need to provide a mocked UserProvider into the context. To do so, just use MultiProvider in the tests to inject the mocked one.
Note the use of #GenerateMocks([UserProvider]). This comes from mockito and it annotates the source to generate the MockUserProvider class. And to generate the *.mocks.dart just run flutter pub run build_runner build.
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'package:provider/provider.dart';
import 'package:so72756235_test_provider/contact_controller.dart';
import 'contact_controller_test.mocks.dart';
#GenerateMocks([UserProvider])
void main() {
testWidgets('ContactController test', (WidgetTester tester) async {
final mockUserProvider = MockUserProvider();
when(mockUserProvider.contact).thenReturn(const Contact(name: 'Test'));
await tester.pumpWidget(
MultiProvider(
providers: [
Provider<UserProvider>(create: (_) => mockUserProvider),
ChangeNotifierProvider(
create: (context) => ContactController(context)),
],
child: Consumer<ContactController>(
builder: (context, value, child) =>
Text(value.newContact.name, textDirection: TextDirection.ltr)),
),
);
expect(find.text('Test'), findsOneWidget);
});
}

How to mock url_launcher package for flutter test?

I used url_launcher: ^6.1.0 in my flutter project.
I start to write tests for my widgets, but the part of widgets that used the url_launcher method to launch an URL, not worked properly when running the test.
One of the methods that I used inside my Widget is like below method:
Future<void> _onTapLink(String? href) async {
if (href == null) return;
// canLaunchUrl method never return anything when we are calling this function inside flutter test
if (await canLaunchUrl(Uri.parse(href))) {
await launchUrl(Uri.parse(href));
} else {
print('cannot launch url: $href');
}
}
canLaunchUrl method never returns anything when we are calling this function inside the flutter test.
I'm looking for a way to mock the url_launcher package for using inside
flutter tests.
To mock url_launcher you may:
Add plugin_platform_interface and url_launcher_platform_interface packages to dev_dependencies section in the pubspec.yaml file.
Implement the mock class. For example, with mocktail the implementation would be:
import 'package:mocktail/mocktail.dart';
import 'package:plugin_platform_interface/plugin_platform_interface.dart';
import 'package:url_launcher_platform_interface/url_launcher_platform_interface.dart';
class MockUrlLauncher extends Mock
with MockPlatformInterfaceMixin
implements UrlLauncherPlatform {}
Notice that here MockPlatformInterfaceMixin mixin is used.
Configure the mock as usual. With mocktail that might be:
MockUrlLauncher setupMockUrlLauncher() {
final mock = MockUrlLauncher();
registerFallbackValue(const LaunchOptions());
when(() => mock.launchUrl(any(), any())).thenAnswer((_) async => true);
return mock;
}
Tell url_launcher to use mocked version by setting it in UrlLauncherPlatform.instance:
final mock = setupMockUrlLauncher();
UrlLauncherPlatform.instance = mock;
This is an article that explicitly explains how to mock or fake the launchUrl function. Here is an example of how to mock it with mocktail. It also uses the ioc_container package to handle substitution with dependency injection.
import 'package:fafsdfsdf/main.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:flutter/material.dart';
class LaunchMock extends Mock {
Future<bool> call(
Uri url, {
LaunchMode? mode,
WebViewConfiguration? webViewConfiguration,
String? webOnlyWindowName,
});
}
void main() {
testWidgets('Test Url Launch', (tester) async {
//These allow default values
registerFallbackValue(LaunchMode.platformDefault);
registerFallbackValue(const WebViewConfiguration());
//Create the mock
final mock = LaunchMock();
when(() => mock(
flutterDevUri,
mode: any(named: 'mode'),
webViewConfiguration: any(named: 'webViewConfiguration'),
webOnlyWindowName: any(named: 'webOnlyWindowName'),
)).thenAnswer((_) async => true);
final builder = compose()
//Replace the launch function with a mock
..addSingletonService<LaunchUrl>(mock);
await tester.pumpWidget(
builder.toContainer()<MyApp>(),
);
//Tap the icon
await tester.tap(
find.byIcon(Icons.favorite),
);
await tester.pumpAndSettle();
verify(() => mock(flutterDevUri)).called(1);
});
}

How do I initialize Firebase App in Flutter widget testing

So I am new in flutter and trying to perform widget testing on my app, but I am keep getting this weird error.
below is my test file
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:folk_team_app/provider/auth_provider.dart';
import 'package:folk_team_app/screens/phone_login.dart';
import 'package:provider/provider.dart';
void main() async {
TestWidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
Widget loginScreen = ChangeNotifierProvider<AuthProvider>(
create: (context) => AuthProvider(),
builder: (context, child) {
return MaterialApp(
home: PhoneLogineScreen(),
);
});
testWidgets('Phone Authetication Page', (WidgetTester tester) async {
// Build our app and trigger a frame.
await tester.pumpWidget(loginScreen);
await tester.pump(const Duration(seconds: 10));
final titleText = find.text('Screen Title');
// Verify that our counter starts at 0.
expect(titleTextt, findsOneWidget);
});
}
here is the error :
You can't directly test Firebase because it requires platform configurations in order to initialize it properly.
You need to mock Firebase in the test environment using mockito.
You may find this comment and tutorial video helpful.

Flutter - Load assets for tests

Not sure if it's a limitation or something, but below code does not load anything. I have some data driven behaviour that I'd like to test isolated.
class Loader
import 'dart:async';
import 'package:flutter/services.dart' show rootBundle;
class Loader {
Future<String> load() async{
return await rootBundle.loadString('assets/json/sketch.json');
}
}
The test
testWidgets('Should parse load sketch.json', (WidgetTester tester) async {
var loaderFuture = new Loader();
Future<String> resultFuture = loaderFuture.load();
resultFuture.then((value) => print(value))
.catchError((error) => print(error));
while(true){};
});
Future does not return neither success nor error and hangs forever. I know
the while(true) locking up the test, but for now I just wanted to see sketch.json printed
Asset location
To use rootBundle in your tests you need this at the beginning of your test programs:
import 'package:flutter_test/flutter_test.dart';
...
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
See the documentation of DefaultAssetBundle it describes using it and a AssetBundle to provide your own assets.
Create a class that wraps the rootBundle:
#injectable
class AssetsManager {
Future<String> loadString(String path) {
return rootBundle.loadString(path);
}
}
Then inject it to your class and in your test override its dependency:
getIt.unregister<AssetsManager>();
getIt.registerSingleton<AssetsManager>(AssetsManagerMock());
Based on your test scenario, Configure what will be returned when calling loadString by using Mocktail's when method.
I'm using get_it for DI. Hope it's clear enough.