I am trying to use the riverpod 2.0 state management with generator and Clean Architecture.
My goal is simple: I do an API call to fetch a document from a server, and if it fails, I fetch the file from assets.
I've already successfully set the data (data sources, model and repo impl.) and domain (entities, repo, use case) layers. Now I am working on the presentation layer but I am sure that I am doing something wrong.
In particular, I don't think that init the data sources and repo impl. in this way is completely correct.
But mainly the problem is that on the page in the data state, I don't receive the Entity. I already tried to change the notifier return type but I was able only to get the State and not the Entity.
Any suggestion?
Use case:
import 'package:example/features/app_language/domain/repositories/available_languages_repository.dart';
import 'package:example/features/app_language/presentation/riverpod/available_languages_state.dart';
class AvailableLanguagesUseCase {
final AvailableLanguagesRepository availableLanguagesRepository;
AvailableLanguagesUseCase({required this.availableLanguagesRepository});
Future<AvailableLanguagesState> getAvailableLanguages() async {
final availableLanguages =
await availableLanguagesRepository.getAvailableLanguages();
return availableLanguages
.fold((error) => const AvailableLanguagesState.error(),
(availableLanguagesEntity) {
print(availableLanguagesEntity);
return AvailableLanguagesState.data(
availableLanguagesEntity: availableLanguagesEntity);
});
}
}
State:
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:example/features/app_language/domain/entities/available_languages_entity.dart';
part 'available_languages_state.freezed.dart';
#freezed
abstract class AvailableLanguagesState with _$AvailableLanguagesState {
///Loading
const factory AvailableLanguagesState.loading() =
_AvailableLanguagesStateLoading;
///Data
const factory AvailableLanguagesState.data(
{required AvailableLanguagesEntity availableLanguagesEntity}) =
_AvailableLanguagesStateData;
///Error
const factory AvailableLanguagesState.error([String? error]) =
_AvailableLanguagesStateError;
}
AsyncNotifier:
import 'package:http/http.dart' as http;
import 'package:example/features/app_language/data/datasources/available_languages_local_data_source.dart';
import 'package:example/features/app_language/data/datasources/available_languages_remote_data_source.dart';
import 'package:example/features/app_language/data/repositories/available_languages_repository_impl.dart';
import 'package:example/features/app_language/domain/usecase/available_languages_use_case.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'available_languages_notifier.g.dart';
#riverpod
class AvailableLanguagesAsyncNotifier
extends _$AvailableLanguagesAsyncNotifier {
#override
Future<void> build() async {
return getAvailableLanguages();
}
Future<void> getAvailableLanguages() async {
final AvailableLanguagesLocalDataSourceImpl
availableLanguagesLocalDataSourceImpl =
AvailableLanguagesLocalDataSourceImpl();
final AvailableLanguagesRemoteDataSourceImpl
availableLanguagesRemoteDataSourceImpl =
AvailableLanguagesRemoteDataSourceImpl(client: http.Client());
final AvailableLanguagesUseCase availableLanguagesUseCase =
AvailableLanguagesUseCase(
availableLanguagesRepository: AvailableLanguagesRepositoryImpl(
availableLanguagesLocalDataSource:
availableLanguagesLocalDataSourceImpl,
availableLanguagesRemoteDataSource:
availableLanguagesRemoteDataSourceImpl));
state = const AsyncValue.loading();
//TODO DELETE DELAY
await Future.delayed(const Duration(seconds: 2));
state = AsyncValue.data(
await availableLanguagesUseCase.getAvailableLanguages());
}
}
Page:
class InitialSetupPage extends StatelessWidget {
const InitialSetupPage({super.key});
#override
Widget build(BuildContext context) {
return Consumer(
builder: (context, ref, child) {
final a = ref.watch(availableLanguagesAsyncNotifierProvider);
print("state: $a");
return a.maybeWhen(
loading: () => Container(
color: Colors.purple,
),
data: (availableLanguagesEntity) =>
Container(color: Colors.green, child: Center()),
error: (error, stackTrace) => Container(
color: Colors.red,
),
orElse: () => Container(
color: Colors.lightBlue,
),
);
},
);
}
}
Thanks in advance
EDIT:
Just to add details to what I've already tried but did not convince me:
Notifier:
import 'package:http/http.dart' as http;
import 'package:example/features/app_language/data/datasources/available_languages_local_data_source.dart';
import 'package:example/features/app_language/data/datasources/available_languages_remote_data_source.dart';
import 'package:example/features/app_language/data/repositories/available_languages_repository_impl.dart';
import 'package:example/features/app_language/domain/usecase/available_languages_use_case.dart';
import 'package:example/features/app_language/presentation/riverpod/available_languages_state.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'available_languages_notifier.g.dart';
#riverpod
class AvailableLanguagesAsyncNotifier
extends _$AvailableLanguagesAsyncNotifier {
#override
Future<AvailableLanguagesState> build() async {
getAvailableLanguages();
return const AvailableLanguagesState.loading();
}
getAvailableLanguages() async {
final AvailableLanguagesLocalDataSourceImpl
availableLanguagesLocalDataSourceImpl =
AvailableLanguagesLocalDataSourceImpl();
final AvailableLanguagesRemoteDataSourceImpl
availableLanguagesRemoteDataSourceImpl =
AvailableLanguagesRemoteDataSourceImpl(client: http.Client());
final AvailableLanguagesUseCase availableLanguagesUseCase =
AvailableLanguagesUseCase(
availableLanguagesRepository: AvailableLanguagesRepositoryImpl(
availableLanguagesLocalDataSource:
availableLanguagesLocalDataSourceImpl,
availableLanguagesRemoteDataSource:
availableLanguagesRemoteDataSourceImpl));
state = const AsyncValue.loading();
//TODO DELETE DELAY
await Future.delayed(const Duration(seconds: 2));
state = AsyncValue.data(
await availableLanguagesUseCase.getAvailableLanguages());
}
}
Page:
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:example/features/app_language/presentation/riverpod/available_languages_notifier.dart';
class InitialSetupPage extends StatelessWidget {
const InitialSetupPage({super.key});
#override
Widget build(BuildContext context) {
return Consumer(
builder: (context, ref, child) {
final a = ref.watch(availableLanguagesAsyncNotifierProvider);
print("state: $a");
return a.maybeWhen(
loading: () => Container(
color: Colors.purple,
),
data: (availableLanguagesEntity) => Container(
color: Colors.green,
child: Center(
child: Text(availableLanguagesEntity.maybeWhen(
data: (availableLanguagesEntity) =>
availableLanguagesEntity.availableLanguagesEntity.first,
orElse: () {
return " ";
})),
)),
error: (error, stackTrace) => Container(
color: Colors.red,
),
orElse: () => Container(
color: Colors.lightBlue,
),
);
},
);
}
}
Found a solution. The main issue was the use case class.
Now it returns Future<Either<Failure, AvailableLanguagesEntity>> and not the AvailableLanguagesState
import 'package:dartz/dartz.dart';
import 'package:example/core/errors/failures.dart';
import 'package:example/features/app_language/domain/entities/available_languages_entity.dart';
import 'package:example/features/app_language/domain/repositories/available_languages_repository.dart';
class AvailableLanguagesUseCase {
final AvailableLanguagesRepository availableLanguagesRepository;
AvailableLanguagesUseCase({required this.availableLanguagesRepository});
Future<Either<Failure, AvailableLanguagesEntity>>
getAvailableLanguages() async {
return await availableLanguagesRepository.getAvailableLanguages();
}
}
This is the Notifier
import 'package:http/http.dart' as http;
import 'package:example/features/app_language/data/datasources/available_languages_local_data_source.dart';
import 'package:example/features/app_language/data/datasources/available_languages_remote_data_source.dart';
import 'package:example/features/app_language/data/repositories/available_languages_repository_impl.dart';
import 'package:example/features/app_language/domain/usecase/available_languages_use_case.dart';
import 'package:example/features/app_language/presentation/riverpod/available_languages_state.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
part 'available_languages_notifier.g.dart';
#riverpod
class AvailableLanguagesNotifier extends _$AvailableLanguagesNotifier {
#override
AvailableLanguagesState build() {
getAvailableLanguages();
return const AvailableLanguagesState.loading();
}
void getAvailableLanguages() async {
final AvailableLanguagesLocalDataSourceImpl
availableLanguagesLocalDataSourceImpl =
AvailableLanguagesLocalDataSourceImpl();
final AvailableLanguagesRemoteDataSourceImpl
availableLanguagesRemoteDataSourceImpl =
AvailableLanguagesRemoteDataSourceImpl(client: http.Client());
final AvailableLanguagesUseCase availableLanguagesUseCase =
AvailableLanguagesUseCase(
availableLanguagesRepository: AvailableLanguagesRepositoryImpl(
availableLanguagesLocalDataSource:
availableLanguagesLocalDataSourceImpl,
availableLanguagesRemoteDataSource:
availableLanguagesRemoteDataSourceImpl));
//TODO DELETE DELAY
await Future.delayed(const Duration(seconds: 2));
final failureOrAvailableLanguagesEntity = await availableLanguagesUseCase.getAvailableLanguages();
failureOrAvailableLanguagesEntity .fold(
(error) => state = const AvailableLanguagesState.error(),
(availableLanguagesEntity) async => state =
AvailableLanguagesState.data(
availableLanguagesEntity: availableLanguagesEntity));
}
}
Page:
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:example/features/app_language/presentation/riverpod/available_languages_notifier.dart';
class InitialSetupPage extends StatelessWidget {
const InitialSetupPage({super.key});
#override
Widget build(BuildContext context) {
return Consumer(
builder: (context, ref, child) {
final a = ref.watch(availableLanguagesNotifierProvider);
print("state: $a");
return a.maybeWhen(
loading: () => Container(
color: Colors.purple,
),
data: (state) => Container(
color: Colors.green,
child: Center(
child: Text(state.availableLanguagesEntity.first),
)),
error: (_) => Container(
color: Colors.red,
),
orElse: () => Container(
color: Colors.lightBlue,
),
);
},
);
}
}
Still have some doubts about the quality of the AvailableLanguagesNotifier
Related
UPDATE
I was able to solve my problem using the FutureBuilder widget. All the changes were in the file_listing.dart file. Here's the updated file:
start of file_listing.dart
import 'package:flutter/material.dart';
import 'dart:async';
import 'package:path/path.dart' as pathpkg;
import 'package:pythonga_expense_estimator/components/bottom_button.dart';
import 'package:pythonga_expense_estimator/services/directory_services.dart';
class FileListing extends StatefulWidget {
FileListing();
#override
State<FileListing> createState() => _FileListingState();
}
class _FileListingState extends State<FileListing> {
Map fileMap = {};
Future<Map> getSavedEstimates() async {
try {
var savedEstimates = await DirectoryHelper.listOfFiles().then((resp) {
return resp;
});
savedEstimates.forEach((key, value) => print(key));
return savedEstimates;
} catch (e) {
throw Exception("Could Not Retrieve list of saved files");
}
}
Future<List<Widget>> fileListMappedAsWidget() async {
var fileHits =
await getSavedEstimates(); //returns Future<Map<dynamic,dynamic>>
List<Widget> newList = [];
fileHits.forEach((k, v) {
newList.add(Row(
children: [
Text(pathpkg.basename(v)),
BottomButton(
buttonTitle: 'Delete',
onPressed: () => () {
setState(() {
k.deleteSync();
fileMap.remove(k);
});
})
],
));
});
// () => newList;
return newList;
// throw ("f");
}
#override
Widget build(BuildContext context) {
return Container(
child: FutureBuilder(
future: fileListMappedAsWidget(),
// future: testRetrieve,
builder: (BuildContext context, AsyncSnapshot<List> snapshot) {
List<Widget> children;
print(snapshot.connectionState);
if (snapshot.hasData) {
List<Widget> childStuff = [];
for (int i = 0; i < snapshot.data!.length; i++) {
childStuff.add(snapshot.data![i]);
}
children = childStuff;
} else if (snapshot.hasError) {
children = <Widget>[
const Icon(Icons.error_outline, color: Colors.red, size: 60),
Padding(
padding: const EdgeInsets.only(top: 16),
child: Text('Error: ${snapshot.error}'))
];
} else {
children = const <Widget>[
SizedBox(
width: 60, height: 60, child: CircularProgressIndicator()),
Padding(
padding: EdgeInsets.only(top: 16),
child: Text('Awaiting Result'),
)
];
}
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: children,
),
);
}),
);
}
}
end of file_listing.dart
ORIGINAL QUESTION
I'm trying to create a page for a Flutter app. It will be the first page that the user sees when they launch the app.
The page will display a list of files, cost estimates that the user has saved previously and a button for them to create a new "estimate" which can then also be saved to a file.
The files are being saved on the device, and I'm using the path_provider and path packages to access them. Reading from and writing to the device file system are asynchronous operations.
I cannot get the list of files to appear automatically when the page loads and would like to get advice on how to do this. The results of the async function are futures of data types and I cannot get them to be just data types.
Here's what I have in terms of code:
(1) main.dart
It specifies the home page of the material app to be StartPage().
start of main.dart
import 'package:flutter/material.dart';
import 'constants/text_constants.dart';
import 'pages/start_page.dart';
void main() => runApp(PythongaVisitCostCalculator());
class PythongaVisitCostCalculator extends StatelessWidget {
const PythongaVisitCostCalculator({Key? key}) : super(key: key);
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: kAppTitle1,
theme: ThemeData(
primarySwatch: Colors.lightBlue,
elevatedButtonTheme: ElevatedButtonThemeData(
style: ButtonStyle(
shape: MaterialStateProperty.all<OutlinedBorder>(
RoundedRectangleBorder(
borderRadius: BorderRadius.circular(50.0),
),
),
),
),
),
home: StartPage(title: kYourTripsDescr));
}
}
end of main.dart
(2) start_page.dart
This is the home page (stateful class) for the application, as specified in main.dart. One of the items that appears is a list of files saved by the user using the FileListing() widget, which is created in the file_listing.dart file, which follows.
start of start_page.dart
import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:pythonga_expense_estimator/components/round_icon_button.dart';
import 'package:pythonga_expense_estimator/components/file_listing.dart';
import 'package:pythonga_expense_estimator/pages/input_page.dart';
import '../constants/text_constants.dart';
class StartPage extends StatefulWidget {
const StartPage({Key? key, required this.title}) : super(key: key);
final String title;
#override
State<StartPage> createState() => _StartPageState();
}
class _StartPageState extends State<StartPage> {
Map fileList = {};
#override
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text(kAppTitle2),
centerTitle: true,
automaticallyImplyLeading: false,
),
body: SafeArea(
child: Column(children: [
Text(kYourEstimatesDescr),
FileListing(),
Row(
children: [
Text(kEstimateTripDescr),
RoundIconButton(
icon: FontAwesomeIcons.plus,
onPressed: () {
Navigator.push(context,
MaterialPageRoute(builder: (context) {
return InputPage(title: 'New Estimate');
}));
})
],
),
]),
),
);
}
}
end of start_page.dart
(3) file_listing.dart
This stateful class returns a widget ,FileListing(), that is used in the StartPage() widget.
start of file_listing.dart
import 'package:flutter/material.dart';
import 'package:path/path.dart' as pathpkg;
import 'package:pythonga_expense_estimator/components/bottom_button.dart';
import 'package:pythonga_expense_estimator/services/directory_services.dart';
class FileListing extends StatefulWidget {
FileListing();
#override
State<FileListing> createState() => _FileListingState();
}
class _FileListingState extends State<FileListing> {
Map fileMap = {};
Future<Map> getSavedEstimates() async {
try {
var savedEstimates = await DirectoryHelper.listOfFiles().then((resp) {
return resp;
});
savedEstimates.forEach((key, value) => print(key));
return savedEstimates;
} catch (e) {
throw Exception("Could Not Retrieve list of saved files");
}
}
Future<List<Widget>> fileListMappedAsWidget() async {
var fileHits = await getSavedEstimates(); //returns Future<Map<dynamic,dynamic>>
List<Widget> newList = [];
fileHits.forEach((k, v) {
newList.add(Row(
children: [
Text(pathpkg.basename(v)),
BottomButton(
buttonTitle: 'Delete',
onPressed: () => () {
setState(() {
k.deleteSync();
fileMap.remove(k);
});
})],
));
});
}
#override
Widget build(BuildContext context) {
// if (fileList.isEmpty) {
if (fileListMappedAsWidget().isEmpty) {
return Container(
child: Row(children: const [
Center(child: Text("No Estimates on File")),
]));
}
return Column(
//children: fileList,
children: fileListMappedAsWidget(),
);
}
}
end of file_listing.dart
Dart Analysis gives me an error on the line:
children: fileListMappedAsWidget(). The error is:
The argument type 'Future<List>' can't be assigned to the parameter type 'List'.
and an error on the line if (fileListMappedAsWidget().isEmpty) {
The error is:
The getter 'isEmpty' isn't defined for the type 'Future<List>
I was expecting to that the return of fileListMappedAsWidget would be a List rather than a Future<List> once the asynchronous method completed and returned its response.
My question is "How can I transform that Future<List> to List so that my page will list the files saved by the user?
(4) directory_services.dart
Here's the code in directory_services.dart that reads the contents of the application data folder and returns a map of {File, filename text}. This code appears to be working correctly.
start of directory_services.dart
import 'dart:io' as io;
import 'package:path_provider/path_provider.dart' as pathprvdrpkg;
import 'package:path/path.dart' as pathpkg;
import '../constants/text_constants.dart';
class DirectoryHelper {
static Future<Map<io.FileSystemEntity, String>> listOfFiles() async {
List<io.FileSystemEntity> fileSystemEntityList =
io.Directory(await localPath()).listSync();
Map<io.FileSystemEntity, String> fileMap = {};
for (int i = 0; i < fileSystemEntityList.length; i++) {
if (pathpkg.extension(fileSystemEntityList[i].path) ==
kEstimateFileExtension) {
fileMap[fileSystemEntityList[i]] = fileSystemEntityList[i].path;
}
}
return fileMap;
}
static localPath() async {
// finds the correct local path using path_provider package
try {
final directory = await pathprvdrpkg.getApplicationDocumentsDirectory();
// print("directory.path in DirectoryPath.localPath");
return directory.path;
} catch (e) {
return Exception('Error: $e');
}
}
}
end of directory_services.dart
Thanks for your help and advice!
I was able to solve my problem using the FutureBuilder widget. All the changes were in the file_listing.dart file. Here's the updated file:
start of file_listing.dart
import 'package:flutter/material.dart';
import 'dart:async';
import 'package:path/path.dart' as pathpkg;
import 'package:pythonga_expense_estimator/components/bottom_button.dart';
import 'package:pythonga_expense_estimator/services/directory_services.dart';
class FileListing extends StatefulWidget {
FileListing();
#override
State<FileListing> createState() => _FileListingState();
}
class _FileListingState extends State<FileListing> {
Map fileMap = {};
Future<Map> getSavedEstimates() async {
try {
var savedEstimates = await DirectoryHelper.listOfFiles().then((resp) {
return resp;
});
savedEstimates.forEach((key, value) => print(key));
return savedEstimates;
} catch (e) {
throw Exception("Could Not Retrieve list of saved files");
}
}
Future<List<Widget>> fileListMappedAsWidget() async {
var fileHits =
await getSavedEstimates(); //returns Future<Map<dynamic,dynamic>>
List<Widget> newList = [];
fileHits.forEach((k, v) {
newList.add(Row(
children: [
Text(pathpkg.basename(v)),
BottomButton(
buttonTitle: 'Delete',
onPressed: () => () {
setState(() {
k.deleteSync();
fileMap.remove(k);
});
})
],
));
});
// () => newList;
return newList;
// throw ("f");
}
#override
Widget build(BuildContext context) {
return Container(
child: FutureBuilder(
future: fileListMappedAsWidget(),
// future: testRetrieve,
builder: (BuildContext context, AsyncSnapshot<List> snapshot) {
List<Widget> children;
print(snapshot.connectionState);
if (snapshot.hasData) {
List<Widget> childStuff = [];
for (int i = 0; i < snapshot.data!.length; i++) {
childStuff.add(snapshot.data![i]);
}
children = childStuff;
} else if (snapshot.hasError) {
children = <Widget>[
const Icon(Icons.error_outline, color: Colors.red, size: 60),
Padding(
padding: const EdgeInsets.only(top: 16),
child: Text('Error: ${snapshot.error}'))
];
} else {
children = const <Widget>[
SizedBox(
width: 60, height: 60, child: CircularProgressIndicator()),
Padding(
padding: EdgeInsets.only(top: 16),
child: Text('Awaiting Result'),
)
];
}
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: children,
),
);
}),
);
}
}
end of file_listing.dart
is there a way to test the content within a show dialog?
I am trying to do BDD in the project, The following is the scenario:
As a User, I would like to add a photo or select one from the gallery so that I can use it on the item.
The following is the code I am using to test it but for some reason, the test fails.
add_item_view.dart
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:my_app_mobile/models/graded_item/graded_item.dart';
import 'package:my_app_mobile/template/index.dart' as template;
import 'package:image_picker/image_picker.dart';
class AddItemView extends HookWidget {
final GradedItem gradedItem;
static final Key photoKey = Key('#photoKey');
static final GlobalKey<FormState> formKey = GlobalKey<FormState>();
final void Function() onPhoto;
final ImagePicker _imagePicker = ImagePicker();
AddItemView({
#required this.gradedItem,
this.onPhoto,
});
#override
Widget build(BuildContext context) {
final _image = useState<File>();
Future getImage() async {
final pickedFile = await _imagePicker.getImage(source: ImageSource.camera);
if (pickedFile != null) {
_image.value = File(pickedFile.path);
} else {
print('No image selected.');
}
}
return Scaffold(
appBar: AppBar(),
body: SingleChildScrollView(
child: Form(
key: formKey,
child: Column(
children: [
GestureDetector(
onTap: () async {
if (onPhoto != null) {
onPhoto();
}
showDialog(
context: context,
barrierColor: Colors.red.withOpacity(.2),
builder: (context) {
return CameraOptions();
},
);
},
child: Container(
key: photoKey,
alignment: Alignment.center,
child: Icon(
Icons.add_a_photo,
color: Theme.of(context).primaryColor,
size: 44.0,
),
height: 100.0,
width: 100.0,
decoration: BoxDecoration(
color: template.colors.grey340,
borderRadius: BorderRadius.circular(10.0),
),
),
),
],
),
),
),
);
}
}
class CameraOptions extends StatelessWidget {
static final Key captureButtonPhotoKey = Key('#captureButtonPhotoKey');
static final Key chooseButtonPhotoKey = Key('#chooseButtonPhotoKey');
static final Key cancelButtonKey = Key('#cancelButtonKey');
final void Function() onCapture;
final void Function() onChoose;
final void Function() onCancel;
CameraOptions({this.onCapture, this.onChoose, this.onCancel});
#override
Widget build(BuildContext context) {
return Center(
child: Container(
height: 200.0,
width: 200.0,
child: Icon(Icons.camera, color: Colors.blue),
),
);
}
}
add_item_view_test.dart
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'my_app_mobile/models/graded_item/graded_item.dart';
import 'my_app_mobile/views/dashboard/children/collection/children/add_item/add_item.dart';
import 'package:mockito/mockito.dart';
void main() {
Widget mountApp({GradedItem gradedItem, void Function() onPhoto}) {
return MaterialApp(
home: AddItemView(
gradedItem: gradedItem,
onPhoto: onPhoto,
),
);
}
testWidgets('should build with no problems', (tester) async {
await tester.pumpWidget(mountApp(gradedItem: GradedItem.generate()));
expect(find.byType(AddItemView), findsOneWidget);
});
group('photo', () {
testWidgets(
'should photo widget be available',
(tester) async {
await tester.pumpWidget(mountApp(
gradedItem: GradedItem.generate(),
));
expect(find.byKey(AddItemView.photoKey), findsOneWidget);
},
);
testWidgets(
'should be able to call onPhoto handler when is available',
(tester) async {
final fn = MockedFunctions();
await tester.pumpWidget(mountApp(
gradedItem: GradedItem.generate(),
onPhoto: fn.onPhoto,
));
expect(find.byKey(AddItemView.photoKey), findsOneWidget);
await tester.tap(find.byKey(AddItemView.photoKey));
verify(fn.onPhoto()).called(1);
},
);
testWidgets(
'should onPhoto handler open camera options',
(tester) async {
await tester.pumpWidget(
MaterialApp(
home: Material(
child: Builder(
builder: (BuildContext context) {
return AddItemView(
gradedItem: GradedItem.generate(),
// onPhoto: fn.onPhoto,
);
},
),
),
),
);
await tester.tap(find.byKey(AddItemView.photoKey));
await tester.pumpAndSettle(const Duration(seconds: 1));
expect(find.byIcon(Icons.camera), findsOneWidget);
},
);
});
}
class MockedFunctions extends Mock {
void onPhoto();
}
Is there a way to do testing on a showDialog function?
Solved, for some reason, the code I posted is working, I restarted my computer and now they are working.
In my Flutter App, I have created 4 Hive Box for storing different values but data is inserting in only one box and when I try to insert data noting happening or not giving any exception or error. I have developed code as it is for all 4 box but I am not getting why my data is not inserting in Hive. If you run this project you understand everything..
First Box Model Class Code:
import 'package:hive/hive.dart';
part 'PasswordModel.g.dart';
#HiveType(typeId: 0)
class PasswordModel {
#HiveField(0)
final String websiteName;
#HiveField(1)
final String websiteAddress;
#HiveField(2)
final String userName;
#HiveField(3)
final String password;
#HiveField(4)
final String notes;
PasswordModel(
{this.websiteName, this.websiteAddress, this.userName, this.password, this.notes});
}
Second Box Model Class:
import 'package:hive/hive.dart';
part 'CardModel.g.dart';
#HiveType(typeId: 1)
class CardModel{
#HiveField(0)
final String cardName;
#HiveField(1)
final String cardNumber;
#HiveField(2)
final String userName;
#HiveField(3)
final String expiration;
#HiveField(4)
final String cvv;
#HiveField(5)
final String pin;
#HiveField(6)
final String note;
CardModel({this.cardName, this.cardNumber, this.userName, this.expiration, this.cvv, this.pin, this.note});
}
main.dart:
import 'package:flutter/material.dart';
import 'package:hive/hive.dart';
import 'package:secret_keeper/screens/SplashScreen.dart';
import 'package:path_provider/path_provider.dart';
import 'package:secret_keeper/Database/Hive/BankModel.dart';
import 'package:secret_keeper/Database/Hive/CardModel.dart';
import 'package:secret_keeper/Database/Hive/NotesModel.dart';
import 'package:secret_keeper/Database/Hive/PasswordModel.dart';
void main(){
runApp(SecretKeeper());
}
class SecretKeeper extends StatefulWidget {
#override
_SecretKeeperState createState() => _SecretKeeperState();
}
class _SecretKeeperState extends State<SecretKeeper> {
#override
void initState() {
// TODO: implement initState
super.initState();
Hive.registerAdapter(PasswordModelAdapter());
Hive.registerAdapter(CardModelAdapter());
Hive.registerAdapter(BankModelAdapter());
Hive.registerAdapter(NotesModelAdapter());
_openBox();
}
Future _openBox() async {
WidgetsFlutterBinding.ensureInitialized();
var dir = await getApplicationDocumentsDirectory();
Hive.init(dir.path);
Box passwordBox = await Hive.openBox<PasswordModel>('passwordBox');
Box cardBox = await Hive.openBox<CardModel>('cardBox');
Box bankBox = await Hive.openBox<BankModel>('bankBox');
Box notesBox = await Hive.openBox<NotesModel>('notesBox');
return;
}
#override
Widget build(BuildContext context) {
return MaterialApp(
title: "Secret Keeper",
debugShowCheckedModeBanner: false,
theme: ThemeData(
accentColor: Colors.white60,
primaryColor: Colors.white,
primaryIconTheme: IconThemeData(color: Colors.black),
fontFamily: "GoogleFonts",
),
home: SplashScreen(),
);
}
}
Class from where I am getting input. I have skipped UI code for better understanding and showed only method which call when User click on Submit button.
Input Class 1 method:
void addDataToHive() {
PasswordModel passwordModel = PasswordModel(
websiteName: websiteNameController.text,
websiteAddress: websiteAddressController.text,
userName: userNameController.text,
password: passwordController.text,
notes: notesController.text
);
var passwordBox = Hive.box<PasswordModel>('passwordBox');
passwordBox.add(passwordModel);
Navigator.pop(context);
}
Input Class 2 method:
void addDataToHive() {
CardModel cardModel = CardModel(
cardName: cardNameController.text,
cardNumber: cardNumberController.text,
userName: userNameController.text,
expiration: expirationController.text,
cvv: cvvController.text,
pin: pinController.text,
note: notesController.text
);
var cardBox = Hive.box<CardModel>('cardBox');
cardBox.add(cardModel);
Navigator.pop(context);
}
This is a class where I show list of data.
First Class Code:
import 'package:flutter/material.dart';
import 'package:hive/hive.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:secret_keeper/Database/Hive/PasswordModel.dart';
import 'package:secret_keeper/screens/home_screen/passwords/ShowData.dart';
class PasswordsNavigation extends StatefulWidget {
#override
_PasswordsNavigationState createState() => _PasswordsNavigationState();
}
class _PasswordsNavigationState extends State<PasswordsNavigation> {
var passwordBox = Hive.box<PasswordModel>('passwordBox');
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
Expanded(
child: _buildListView(),
)
],
),
);
}
Widget _buildListView() {
return WatchBoxBuilder(
box: passwordBox,
builder: (context, box) {
Map<dynamic, dynamic> raw = box.toMap();
List list = raw.values.toList();
return ListView.builder(
shrinkWrap: true,
itemCount: list.length,
itemBuilder: (context, index) {
PasswordModel passwordModel = list[index];
return ListTile(
title: Text(passwordModel.websiteName),
subtitle: Text(passwordModel.websiteAddress),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
icon: Icon(Icons.delete),
onPressed: () {
passwordBox.deleteAt(index);
},
)
],
),
onTap: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (_) => ShowData(
id: index,
)));
},
);
},
);
},
);
}
}
Second Class Code:
import 'package:flutter/material.dart';
import 'package:hive/hive.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:secret_keeper/Database/Hive/CardModel.dart';
import 'package:secret_keeper/screens/home_screen/cards/ShowData.dart';
class CardsNavigation extends StatefulWidget {
#override
_CardsNavigationState createState() => _CardsNavigationState();
}
class _CardsNavigationState extends State<CardsNavigation> {
var cardBox = Hive.box<CardModel>('cardBox');
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
Expanded(
child: _buildListView(),
)
],
),
);
}
Widget _buildListView() {
return WatchBoxBuilder(
box: cardBox,
builder: (context, box) {
Map<dynamic, dynamic> raw = box.toMap();
List list = raw.values.toList();
return ListView.builder(
shrinkWrap: true,
itemCount: list.length,
itemBuilder: (context, index) {
CardModel cardModel = list[index];
return ListTile(
title: Text(cardModel.cardName),
subtitle: Text(cardModel.cardNumber),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
icon: Icon(Icons.delete),
onPressed: () {
cardBox.deleteAt(index);
},
)
],
),
onTap: () {
Navigator.of(context).push(MaterialPageRoute(
builder: (_) => ShowData(
id: index,
),
),
);
},
);
},
);
},
);
}
}
Same I have extra 2 box, but I add reference of 2 Box only, So my problem is data adding in only first box and remaining box not getting any data, but I don't know why?
For Full code Follow this Link: https://github.com/jaydip-pawar/Password-Manager-Flutter.git
I have completed the following tutorial https://dev.to/amartyadev/flutter-app-authentication-with-django-backend-1-21cp which although useful, has left me still pretty clueless how to move forward. I am able to login and the user details are saved via a UserRepository class to a sqlite database locally, including the authentication token, but I have no idea how to access this when trying to make repeat requests to the server for data. there are no problems with the existing code but I will present several pages so you can see what I have done and am trying to achieve and how it is put together.
main.dart:
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:bloc/bloc.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import './repository/user_repository.dart';
import './bloc/authentication/authentication_bloc.dart';
import './screens/splash_screen.dart';
import './screens/login/login_screen.dart';
import './screens/home_screen.dart';
import './widgets/loading_indicator.dart';
import './widgets/video_widget.dart';
import './screens/home_screen.dart';
const welcomeUrl = 'https://soundjudgement.github.io/static-sf/tour.mp4';
class SimpleBlocDelegate extends BlocDelegate {
#override
void onEvent(Bloc bloc, Object event) {
super.onEvent(bloc, event);
print(event);
}
#override
void onTransition(Bloc bloc, Transition transition) {
super.onTransition(bloc, transition);
print(transition);
}
#override
void onError(Bloc bloc, Object error, StackTrace stacktrace) {
super.onError(bloc, error, stacktrace);
}
}
void main() {
BlocSupervisor.delegate = SimpleBlocDelegate();
final userRepository = UserRepository();
runApp(BlocProvider<AuthenticationBloc>(
create: (context) {
return AuthenticationBloc(userRepository: userRepository)
..add(AppStarted());
},
child: SundayFundayApp(userRepository: userRepository),
));
}
class SundayFundayApp extends StatelessWidget {
// This widget is the root of your application.
final UserRepository userRepository;
SundayFundayApp({Key key, #required this.userRepository}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'The Yard Mobile App',
theme: ThemeData(
primarySwatch: Colors.yellow,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: BlocBuilder<AuthenticationBloc, AuthenticationState>(
builder: (context, state) {
if (state is AuthenticationUnintialized) {
return SplashPage();
}
if (state is AuthenticationAuthenticated) {
return HomeScreen();
}
if (state is AuthenticationUnauthenticated) {
return LogInScreen(
userRepository: userRepository,
);
}
if (state is AuthenticationLoading) {
return LoadingIndicator();
}
},
),
);
}
}
user_repository.dart
import 'dart:async';
import '../models/user_model.dart';
import 'package:meta/meta.dart';
import '../models/api_model.dart';
import '../api_connection/api_connection.dart';
import '../dba/user_dba.dart';
class UserRepository {
final userDao = UserDba();
Future<User> authenticate({
#required String username,
#required String password,
}) async {
UserLogin userLogin = UserLogin(username: username, password: password);
Token token = await getToken(userLogin);
User user = User(
id: 0,
username: username,
token: token.token,
);
return user;
}
Future<void> persistToken({#required User user}) async {
// write token with the user to the database
await userDao.createUser(user);
}
Future<void> deleteToken({#required int id}) async {
await userDao.deleteUser(id);
}
Future<bool> hasToken() async {
bool result = await userDao.checkUser(0);
return result;
}
}
user_dao.dart:
import '../database/user_database.dart';
import '../models/user_model.dart';
class UserDao {
final dbProvider = DatabaseProvider.dbProvider;
Future<int> createUser(User user) async {
final db = await dbProvider.database;
var result = db.insert(userTable, user.toDatabaseJson());
return result;
}
Future<int> deleteUser(int id) async {
final db = await dbProvider.database;
var result = await db
.delete(userTable, where: "id = ?", whereArgs: [id]);
return result;
}
Future<bool> checkUser(int id) async {
final db = await dbProvider.database;
try {
List<Map> users = await db
.query(userTable, where: 'id = ?', whereArgs: [id]);
if (users.length > 0) {
return true;
} else {
return false;
}
} catch (error) {
return false;
}
}
}
login_screen.dart:
import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import '../../repository/user_repository.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../bloc/authentication/authentication_bloc.dart';
import './bloc/login_bloc.dart';
import 'login_form.dart';
class LogInScreen extends StatelessWidget {
final UserRepository userRepository;
LogInScreen({Key key, #required this.userRepository})
: assert(userRepository != null),
super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
// Here we take the value from the MyHomePage object that was created by
// the App.build method, and use it to set our appbar title.
title: Text('The Yard App'),
),
body: Container(
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage('assets/images/theyardbook.png'),
fit: BoxFit.cover,
)
),
child: BlocProvider(
create: (context) {
return LoginBloc(
authenticationBloc: BlocProvider.of<AuthenticationBloc>(context),
userRepository: userRepository,
);
},
child: Container(
child: Scaffold(
backgroundColor: Colors.transparent,
body: Container(
width: MediaQuery.of(context).size.width,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
color: Colors.transparent,
),
child: Padding(
padding: EdgeInsets.all(23),
child: LoginForm(),
),
),
),
),
),
),
);
}
}
Once the user is logged in, the app takes you to a home screen and it is this screen I have placed a button to take me to another screen which will fetch data from the server to display. For now the only data available is the one single registered user which is fine, I just need to see how it works because I am totally stuck and have been for 3 days. What I need to know, is how do I access the authentication token?? I am assuming (hoping) the process will be the same accessing any data saved to a local db.
TIA
I can copy and paste more code but I feel like there is a lot there and the tutorial is pretty clear, it just help me with this final
So here is the code in the user-dao, which I just need to import in any widget/class I am needing to access the server. Seems obvious and totally straight forward but amongst all the Dart code which I am unfamiliar with and the many files used in the bloc pattern, I think my head was getting confused.
Future<String> getUserToken(int id) async {
final db = await dbProvider.database;
try {
var res = await db.rawQuery("SELECT token FROM userTable WHERE id=0");
return res.isNotEmpty ? (User.fromDatabaseJson(res.first)).token : null;
} catch (err) {
return null;
}
}
Here is the new users_screen code, using FutureBuilder
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../widgets/logout_button.dart';
import '../repository/user_repository.dart';
import 'package:http/http.dart' as http;
import '../dao/user_dao.dart';
import '../api_connection/api_connection.dart';
import '../models/user_model.dart';
class UserScreen extends StatefulWidget {
#override
_UserScreenState createState() => _UserScreenState();
}
class _UserScreenState extends State<UserScreen> {
Future<User> futureUser;
#override
void initState() {
super.initState();
futureUser = getUser();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('The Yard Users'),
),
body: Container(
child: FutureBuilder( // use a future builder widget
future: futureUser, // assign the future
builder: (context, snapshot) {
if (snapshot.hasData) {
return Column(
// show your layout if future is done
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Padding(
padding: EdgeInsets.only(left: 30.0),
child: Text(
'Username: ${snapshot.data.username}', // get the username from the snapshot
style: TextStyle(
fontSize: 24.0,
),
),
),
// Logout button
Padding(
padding: EdgeInsets.fromLTRB(34.0, 20.0, 0.0, 0.0),
child: Container(
width: MediaQuery.of(context).size.width * 0.85,
height: MediaQuery.of(context).size.width * 0.16,
child: LogoutButton()
),
),
],
);
} else {
return CircularProgressIndicator(); // show a progress indicator while future is executing
}
},
),
),
);
}
}
I was create SharedPreferences to save user loading in logon page. Then data of user will be save in SharedPreferences and move to main page. But my problem now in main page I need use this variable in different places in main page. But I cant do that.
I need to make variable of logindata can use in each places in main page I try to use in drawer to make logout. No I get error as:
Undefined name 'logindata'.
this is my code:
void initial() async {
logindata = await SharedPreferences.getInstance();
setState(() {
username = logindata.getString('username');
return username;
});
}
my full code:
import 'package:flutter/material.dart';
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:shared_preferences/shared_preferences.dart';
import 'addnewtopics.dart';
import 'DetilesOfMainPage.dart';
import 'loginpage.dart';
class MyApp extends StatelessWidget {
final String email;
MyApp({Key key, #required this.email}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('JSON ListView')
),
drawer: Drawer(
child: ListView(
padding: EdgeInsets.zero,
children: <Widget>[
DrawerHeader(
child: Text('Drawer Header'),
decoration: BoxDecoration(
color: Colors.blue,
),
),
ListTile(
title: Text('Item 1'),
onTap: () {
logindata.setBool('login', true);// here I need to use It ========================
Navigator.pushReplacement(context,
new MaterialPageRoute(builder: (context) => LoginUser()));
Navigator.pop(context);
},
),
ListTile(
title: Text('Item 2'),
onTap: () {
// Navigator.pop(context);
},
),
],
),
),
body: JsonImageList(),
floatingActionButton: FloatingActionButton(
onPressed: () {
Navigator.push(context, MaterialPageRoute(builder: (context) => UploadImageDemo()
),);
},
child: Icon(Icons.add),
),
));
}
}
class Flowerdata {
int id;
String flowerName;
String flowerImageURL;
Flowerdata({
this.id,
this.flowerName,
this.flowerImageURL
});
factory Flowerdata.fromJson(Map<String, dynamic> json) {
return Flowerdata(
id: json['id'],
flowerName: json['nametopics'],
flowerImageURL: json['image']
);
}
}
class JsonImageList extends StatefulWidget {
JsonImageListWidget createState() => JsonImageListWidget();
}
class JsonImageListWidget extends State {
SharedPreferences logindata;
String username;
#override
void initState() {
// TODO: implement initState
super.initState();
initial();
}
void initial() async {
logindata = await SharedPreferences.getInstance();
setState(() {
username = logindata.getString('username');
return username;
});
}
final String apiURL = 'http://xxxxxxxxx/getFlowersList.php';
Future<List<Flowerdata>> fetchFlowers() async {
var response = await http.get(apiURL);
if (response.statusCode == 200) {
final items = json.decode(response.body).cast<Map<String, dynamic>>();
List<Flowerdata> listOfFruits = items.map<Flowerdata>((json) {
return Flowerdata.fromJson(json);
}).toList();
return listOfFruits;
}
else {
throw Exception('Failed to load data from Server.');
}
}
getItemAndNavigate(String item, BuildContext context){
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => SecondScreen(itemHolder : item)
)
);
}
#override
Widget build(BuildContext context) {
return FutureBuilder<List<Flowerdata>>(
future: fetchFlowers(),
builder: (context, snapshot) {
if (!snapshot.hasData) return Center(
child: CircularProgressIndicator()
);
return ListView(
children: snapshot.data
.map((data) => Column(children: <Widget>[
GestureDetector(
onTap: ()=>{
getItemAndNavigate(data.flowerName, context)
},
child: Row(
children: [
Container(
width: 200,
height: 100,
margin: EdgeInsets.fromLTRB(10, 0, 10, 0),
child: ClipRRect(
borderRadius: BorderRadius.circular(8.0),
child:
Image.network(data.flowerImageURL,
width: 200, height: 100, fit: BoxFit.cover,))),
Flexible(child:
Text(data.flowerName,
style: TextStyle(fontSize: 18)))
]),),
Divider(color: Colors.black),
],))
.toList(),
);
},
);
}
}
Anyone know how can make that?
You need var keyword, in your case you can directly use
var logindata = await SharedPreferences.getInstance();
You do not need to make it global, because SharedPreferences.getInstance() is Singleton
Every time you use var logindata = await SharedPreferences.getInstance(); will get the same instance
Also there is no performance issue when you call getInstance(), because it's cached, you can see source code snippet below
class SharedPreferences {
SharedPreferences._(this._preferenceCache);
...
static Future<SharedPreferences> getInstance() async {
if (_completer == null) {
_completer = Completer<SharedPreferences>();
try {
final Map<String, Object> preferencesMap =
await _getSharedPreferencesMap();
_completer.complete(SharedPreferences._(preferencesMap));
} on Exception catch (e) {
// If there's an error, explicitly return the future with an error.
// then set the completer to null so we can retry.
_completer.completeError(e);
final Future<SharedPreferences> sharedPrefsFuture = _completer.future;
_completer = null;
return sharedPrefsFuture;
}
}
return _completer.future;
When you declare a String outside of class and does not contain _ before variable name like _localString it become global
String globalString = ""; //global, import can be seen
String _localString = ""; //local and can only be seen in this file, import can not seen
void main() async{
var logindata = await SharedPreferences.getInstance();
runApp(MyApp());
}
You simply need to put your variable outside of any class or method. An example is to create a file globals.dart then put all your globals in it and import the file when you need.
Example
// globals.dart
String globalString;
int globalInt;
bool globalBool;
// in any other file
import 'globals.dart' as globals;
globals.globalString = "Global String";