How to access one class method from another class in dart? - dart

I'm new to dart. Currently, working on a mobile app through flutter. I have a Helper class which has some common methods which I've planned throughout the app. I've included that Helper class in another class. But, can't able to fig. out how to access its methods.
My commom Helper class code:
import 'dart:async';
import 'dart:convert';
import 'package:shared_preferences/shared_preferences.dart';
class Helper {
Map userDetails = {};
Future<SharedPreferences> _prefs = SharedPreferences.getInstance();
// --- Method for getting user details from shared preference ---
Future<Map>getUserDetailsFromSharedPreference () async {
try {
final SharedPreferences prefs = await _prefs;
if(prefs.getString('user') != null) {
this.userDetails = json.decode(prefs.getString('user'));
} else {
print('Shared preference has no data');
}
} catch (e){
print('Exception caught at getUserDetails method');
print(e.toString());
}
return this.userDetails;
}
}
Here is my main program code where I've included the Helper class & trying to access it's getUserDetailsFromSharedPreference (). In this case, I'm getting an error like Only static memebers can be accessed in initializers. I also tried to extends Helper class in UserProfile class. But, there also I'm getting a different kind of errors. Can't able to identify how to do this thing.
import 'package:flutter/material.dart';
import 'helper.dart';
class UserProfile extends StatefulWidget {
#override
UserProfileState createState() => new UserProfileState();
}
class UserProfileState extends State<UserProfile> {
Helper helper = new Helper();
var userData = helper.getUserDetailsFromSharedPreference();
}
#Günter Zöchbauer I've made my Helper.dart file like this as you've suggested -
import 'dart:async';
import 'dart:convert';
import 'package:shared_preferences/shared_preferences.dart';
class Helper {
Map userDetails = {};
Future<SharedPreferences> _prefs = SharedPreferences.getInstance();
static Helper _instance;
factory Helper() => _instance ??= new Helper._();
Helper._();
// --- Method for getting user details from shared preference ---
Future<Map>getUserDetailsFromSharedPreference () async {
try {
final SharedPreferences prefs = await _prefs;
if(prefs.getString('user') != null) {
this.userDetails = json.decode(prefs.getString('user'));
} else {
print('Shared preference has no data');
}
} catch (e){
print('Exception caught at getUserDetails method');
print(e.toString());
}
return this.userDetails;
}
}
Now, in my tryint to access that getUserDetailsFromSharedPreference() method I'm getting the same error Only static memebers can be accessed in initializers .

You could ensure a singleton instance of the class using a public factory constructor with a private regular constructor:
class Helper {
static Helper _instance;
factory Helper() => _instance ??= new Helper._();
Helper._();
...
}
If you call new Helper(), you'll always get the same instance.
You need to import the file that contains class Helper {} everywhere where you want to use it.
??= means new Helper._() is only executed when _instance is null and if it is executed the result will be assigned to _instance before it is returned to the caller.
update
getUserDetailsFromSharedPreference is async and can therefore not be used in the way you use it, at least it will not lead to the expected result. getUserDetailsFromSharedPreference returns a Future that provides the result when the Future completes.
class UserProfileState extends State<UserProfile> {
Helper helper = new Helper();
Future<Map> _userData; // this with ??= of the next line is to prevent `getUserDetailsFromSharedPreference` to be called more than once
Future<Map> get userData => _userData ??= helper.getUserDetailsFromSharedPreference();
}
If you need to access userData you need to mark the method where you do with async and use await to get the result.
foo() async {
var ud = await userData;
print(ud);
}

To access other class method you can simply put static on the method.
class Helper {
static printing(String someText){
print(someText);
}
}
void main() {
Helper.printing('Hello World!');
}

I think this question is more related to accessing one class data in another class. So I explained on the basis of my understanding of the question but if I'm not correct about it.
but if you want to access data of class A, not directly but through class B.
so first you have to make an object of A in class B but remember one thing you would have to make the object static in order to get access to the data of class A within Class B
If you still are confused about all this, I made a solution video.
Check it out: https://youtu.be/shK7ZraruCI

Related

Dart create class instance by string with class name

I want to invoke functions of a class by their names inside a string. I know my best option are Mirrors.
var ref = reflect(new TestClass());
ref.invoke(Symbol("test"), []);
It works fine, I can call the function test by a string. But I also want to put "TestClass" inside a string. Is it possible somehow ?
var ref = reflect("TestClass");
ref.invoke(Symbol("test"), []);
Jonas
You can do something like this:
import 'dart:mirrors';
class MyClass {
static void myMethod() {
print('Hello World');
}
}
void main() {
callStaticMethodOnClass('MyClass', 'myMethod'); // Hello World
}
void callStaticMethodOnClass(String className, String methodName) {
final classSymbol = Symbol(className);
final methodSymbol = Symbol(methodName);
(currentMirrorSystem().isolate.rootLibrary.declarations[classSymbol]
as ClassMirror)
.invoke(methodSymbol, <dynamic>[]);
}
Note, that this implementation does require that myMethod is static since we are never creating any object but only operate directly on the class itself. You can create new objects from the class by calling newInstance on the ClassMirror but you will then need to call the constructor.
But I hope this is enough. If not, please ask and I can try add some more examples.

Dart: Using Interface gives the error: "isn't a valid override of"

I've created a small interface:
import ...
abstract class IController {
void navigateTo(BuildContext context, String routeName);
Future<LocationData> get location;
// registration process
void registerGender(Gender gender);
void registerBirthday(DateTime birthday);
Future<bool> register(String email, String password);
}
And then I tried to implement this:
import ...
class Controller implements IController {
static final Controller _instance = Controller._internal();
final ServiceAuthenticate _serviceAuth = ServiceAuthenticate();
final ServiceDatabase _serviceDb = ServiceDatabase();
final ServiceGPS _serviceGPS = ServiceGPS();
User _user;
String _routeName;
UserData _userData;
Controller._internal() {
this._routeName = ROUTE_WELCOME;
}
factory Controller() => _instance;
void navigateTo(BuildContext context, String routeName) {
this._routeName = routeName;
Navigator.pushReplacementNamed(context, routeName);
}
Future<LocationData> get location async{
this._userData.location = await this._serviceGPS.location;
print(this._userData.location);
return this._userData.location;
}
void registerGender(Gender gender){
this._userData = UserData();
this._userData.gender = gender;
}
void registerBirthday(DateTime birthday) {
this._userData.birthday = birthday;
}
Future<bool> register(String email, String password) async {
User user = await this._serviceAuth.registerWithEmailAndPassword(email, password);
if(user == null){
return false;
}
this._user = user;
return true;
}
}
But that code produces the following error:
error: 'Controller.navigateTo' ('void Function(BuildContext, String)') isn't a valid override of 'IController.navigateTo' ('void Function(dynamic, String)'). (invalid_override at [prototype] lib\controller\controller.dart:30)
It looks like Dart thinks, that the BuildContext in the IController is dynamic, but this is obviously not the case.
How can I fix this? I'm new to Dart and don't know what to do.
Thanks for help :)
I'm stupid.
My import statement was wrong.
The line
import 'package:prototype/Controller/IController.dart';
produced this error, because the folder controller starts with a lowercase Letter.
The correct import statement is
import 'package:prototype/controller/IController.dart';
But regardless of my stupid mistake is the error message quite interesting.
A had a similar error and in my case the problem was that the return type of the buggy function was such that there were two different classes in the codebase with the same name. And the interface was using one and the implementation the other.
The one line answer is :
Your import statement is wrong.
But now , you need to take care in which file the import statement is going wrong.
There can be many scenarios, but I would like to give an example where I was stuck.
I had two different files in different package, but both files were importing some method where the method names were same.
So while importing the file which contain this method, I had imported the same name method from one file, and at other place, the same name method from second file.
So that's where everything went wrong!
So if import file is correct in the file which is giving some error, check the other dependent file, where same method import statement is written, that may be wrong.

Flutter Dart Singleton Data 'Lost'

I want to use the following class to store account data to make it accessible to every part of the app.
class AccountData {
static AccountData _instance = new AccountData._internal();
static GoogleSignInAccount _googleData;
factory AccountData() {
return _instance;
}
AccountData._internal();
static void setData(GoogleSignInAccount googleData)
{
_googleData = googleData;
}
GoogleSignInAccount get getData{
return _googleData;
}
String getUserID(){
return "5";
}
List<Workout> getWorkouts(){
return null;
}
Workout getWorkoutByDate(String workoutID, String date)
{
return null;
}
}
I setup the google account after the user logged in, using the following code.
_googleSignIn.onCurrentUserChanged.listen((GoogleSignInAccount account) {
AccountData.setData(account);
print("Logged in, id: " + account.id);
runApp(new HomeState());
});
At this point I can access all fields of account and everything is working fine. The only way to get past the login screen is through this part of the code. Therefore the account data always gets assigned.
#override
void initState() {
super.initState();
AccountData d = new AccountData();
print(d.getData);
}
If I try to access the account data in the HomeState, the GoogleSignInAccount that gets returned is null which it shouldn't be.
I hope that someone knows why this happens and knows how to solve the problem.
You are storing the data in a static variable of the class from a static method AccountData.setData(account);
You should also statically access the data to recover them:
AccountData.getData();
Previously, you need to define the method as static in the class body:
static GoogleSignInAccount get getData{
return _googleData;
}
Then, since everything is static for the _googleData you don't need to create an instance of AccountData.
Otherwise, you could change all of them to be instance variable and methods, and you should create the instance upfront.

How to test StreamSubscription and create Events in Dart?

I want to write some unit tests around an abstract Uploader class that I have written like so:
abstract class Uploader {
Future<StreamSubscription> subscribe(String filename, void onEvent(Event event));
}
class FirebaseUploader implements Uploader {
Future<StreamSubscription> subscribe(String filename, void onEvent(Event event)) async {
String userId = await auth.signInAnonymously();
DatabaseReference databaseReference = _databaseReference(userId, filename);
return databaseReference.onValue.listen(onEvent);
}
}
class UploaderMock implements Uploader {
Future<StreamSubscription> subscribe(String filename, void onEvent(Event event)) async {
Event event = new Event(); // The class 'Event' doesn't have a default constructor.
return Future.value(null);
}
}
The trouble is, I can't work out how to create my own Events in my UploaderMock, so I can call onEvent. If I try to create a new Event(), I get the following error:
The class 'Event' doesn't have a default constructor.
This is because Event has a private constructor:
Event._(this._data) : snapshot = new DataSnapshot._(_data['snapshot']);
This makes sense for production, but it doesn't really work for testing.
Any ideas? How can I test code that uses StreamSubscription?
You can implements Event on a custom class.
class Bar {
Bar._() {}
}
class Foo implements Bar {
Foo();
}
You can't, but you can make them public and annotate it with
#visibleForTesting to get an DartAnalyzer warning when they are
accessed from code that is not in in the same library or in test/
answered here How to test private functions/methods in Flutter?

Dart - how to mock a method that returns a future

I have a class that defines a method that returns a Future. The Future contains a list of class that also return a future.
class User{
Future<List<Album>> albums(){
};
}
class Album{
Future<List<Photos>> photos(){
}
};
What is the best way to mock the method in these classes when testing another class?
The class I am trying to test looks a bit like
class Presenter {
Presenter( User user){
user.albums().then( _processAlbums);
}
_processAlbums(List<Album> albums) {
albums.forEach( (album)=>album.photos.then( _processPhotos));
}
_processPhotos(List<Photo> photos) {
....stuff
}
}
I tried writing a unit test like this
class MockUser extends Mock implements User{}
class MockAlbum extends Mock implements Album{}
class MockPhoto extends Mock implements Photo{}
class MockFutureList<T> extends Mock implements Future<T>{
MockFutureList( List<T> items){
when( callsTo( "then")).thenReturn( items);
}
}
void main(){
test("constuctor should request the albums from the user ",(){
MockUser user = new MockUser();
MockAlbum album = new MockAlbum();
List<Album> listOfAlbums = [ album];
MockPhoto photo = new MockPhoto();
List<Album> listOfPhotos = [ album];
user.when( callsTo( "albums")).thenReturn( new MockFutureList(listOfAlbums));
album.when( callsTo( "photos")).thenReturn( new MockFutureList( listOfPhotos));
PicasaPhotoPresentor underTest = new PicasaPhotoPresentor( view, user);
user.getLogs( callsTo( "albums")).verify( happenedOnce);
album.getLogs( callsTo( "photos")).verify( happenedOnce);
});
}
This allowed me to test that the constructor called the user.photos() method, but not that the album.photos() method was called.
I am not sure that mocking a Future is a good idea - Would it not be better to create a 'real' Future that contains a list of Mocks?
Any ideas would be very helpful!
Since you're only interested in verifying that methods in User and Album are called, you won't need to mock the Future.
Verifying the mocks gets a bit tricky here, because you're chaining futures inside the constructor. With a little understanding of how the event loop works in Dart, I recommend using a future and calling expectAsync after you create your presenter.
The expectAsync function tells the unit test library to wait until it's called to verify your tests. Otherwise the test will complete successfully without running your expectations.
With this, here's what your test should would look like:
import 'package:unittest/unittest.dart';
class MockUser extends Mock implements User {}
class MockAlbum extends Mock implements Album {}
void main() {
test("constuctor should request the albums from the user ", () {
var user = new MockUser();
var album = new MockAlbum();
user.when(callsTo("albums")).thenReturn(new Future(() => [album]));
var presenter = new PicasaPhotoPresentor(view, user);
// Verify the mocks on the next event loop.
new Future(expectAsync(() {
album.getLogs(callsTo("photos")).verify(happendOnce);
}));
});
}
Here is how I managed to do it
1) Define FutureCallbackMock
class FutureCallbackMock extends Mock implements Function {
Future<void> call();
}
2) get function from a mock and set it up
FutureCallback onPressed = FutureCallbackMock().call;
completer = Completer<void>();
future = completer.future;
when(onPressed()).thenAnswer((_) => future);
3) Verify like so
verify(onPressed()).called(1);
4) Complete the future if needed:
completer.complete();
NOTE: in flutter tests I had to wrap my test in tester.runAsync like so
testWidgets(
'when tapped disables underlying button until future completes',
(WidgetTester tester) async {
await tester.runAsync(() async {
// test here
});
});
I was able to do this with Mocktail. This is the article that this is from, and explains how to integrate it into your app. This is a full widget test and depends on this gist code.
The crux is that you need to declare a Mock class that has a call method. Then, you can then mock the top-level function that returns a Future. You can use the when and verify methods with this.
//Gist code
import 'package:gist/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);
});
}

Resources