flutter - How to download asset when apps launched and used it - android

currently, my apps have many assets (images, sound, font, json, SQL-lite database file, etc). All have defined in pubspec.yaml
However, due to a request to reduce APK size, I need some of them to be downloaded when Apps be launched and save it to storage, so no need to download it next time.
if assets are not yet ready, it should waiting a sec and show loading bar circle.
The question is how to do this thing, Any Example?

The easiest way to do it is to download your files as zip (archived file) and unpack them in the path of the application storage directory getApplicationDocumentsDirectory
You will use this list of packages:
archive ,
http and
path_provider
The pubspec.yaml will look like
version: 1.0.0+1
environment:
sdk: ">=2.1.0 <3.0.0"
dependencies:
flutter:
sdk: flutter
path_provider: ^1.1.0
http: ^0.12.0+2
archive: ^2.0.8
dev_dependencies:
flutter_test:
sdk: flutter
uses-material-design: true
assets:
- assets/images/
The main.dart file which coronations your app will look like
Note that api is the URL of your file without the file name.
main.dart
import 'dart:io';
import 'package:archive/archive.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:path_provider/path_provider.dart';
import 'data.dart';
const api =
'https://firebasestorage.googleapis.com/v0/b/playground-a753d.appspot.com/o';
enum AppTheme { candy, cocktail }
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(AppTheme.candy),
);
}
}
class MyHomePage extends StatefulWidget {
final AppTheme theme;
MyHomePage(this.theme);
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
AppTheme _theme;
String _dir;
List<String> _images;
#override
void initState() {
super.initState();
_theme = widget.theme;
_images = data[_theme];
}
#override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(
child: Icon(Icons.style),
onPressed: () async {
if (_theme == AppTheme.candy) {
await _downloadAssets('cocktail');
}
setState(() {
_theme =
_theme == AppTheme.candy ? AppTheme.cocktail : AppTheme.candy;
_images = data[_theme];
});
},
),
body: ListView.builder(
itemCount: _images.length,
itemBuilder: (BuildContext context, int index) {
return _getImage(_images[index], _dir);
}),
);
}
Widget _getImage(String name, String dir) {
if (_theme != AppTheme.candy) {
var file = _getLocalImageFile(name, dir);
return Image.file(file);
}
return Image.asset('assets/images/$name');
}
File _getLocalImageFile(String name, String dir) => File('$dir/$name');
Future<void> _downloadAssets(String name) async {
if (_dir == null) {
_dir = (await getApplicationDocumentsDirectory()).path;
}
if (!await _hasToDownloadAssets(name, _dir)) {
return;
}
var zippedFile = await _downloadFile(
'$api/$name.zip?alt=media&token=7442d067-a656-492f-9791-63e8fc082379',
'$name.zip',
_dir);
var bytes = zippedFile.readAsBytesSync();
var archive = ZipDecoder().decodeBytes(bytes);
for (var file in archive) {
var filename = '$_dir/${file.name}';
if (file.isFile) {
var outFile = File(filename);
outFile = await outFile.create(recursive: true);
await outFile.writeAsBytes(file.content);
}
}
}
Future<bool> _hasToDownloadAssets(String name, String dir) async {
var file = File('$dir/$name.zip');
return !(await file.exists());
}
Future<File> _downloadFile(String url, String filename, String dir) async {
var req = await http.Client().get(Uri.parse(url));
var file = File('$dir/$filename');
return file.writeAsBytes(req.bodyBytes);
}
}
Then you have to list all files and added them (logically to the corresponding theme)
data.dart:
import 'main.dart' show AppTheme;
const Map<AppTheme, List<String>> data = const {
AppTheme.candy: [
'art-background-blue-1289363.jpg',
'assortment-bright-candy-1043519.jpg',
'bright-candies-cherry-1405760.jpg',
'bright-candies-colorful-539447.jpg',
'bright-candy-chewy-1328885.jpg',
],
AppTheme.cocktail: [
'alcohol-alcoholic-beverage-beverage-1304540.jpg',
'alcohol-alcoholic-beverage-beverage-1723638.jpg',
'alcohol-black-background-close-up-800390.jpg',
'alcoholic-beverage-beverage-cocktail-970197.jpg',
'bar-beverage-blur-338713.jpg',
]
};
For more information check this Github project and Medium article

Related

NewtworkImageLoadException - I can't load image to my emulator

I wrote this code recently but it doesn't get me the result!
import 'package:flutter/material.dart';
import 'package:http/http.dart' show get;
import 'package:pics/src/widgets/image_list.dart';
import 'models/image_model.dart';
import 'dart:convert';
class App extends StatefulWidget {
createState() {
return AppState();
}
}
class AppState extends State<App> {
int counter = 0;
List<ImageModel> images = [];
void fetchImage() async {
counter++;
final Uri rep =
Uri.parse('https://jsonplaceholder.typicode.com/photos/$counter');
var response = await get(rep);
var imageModel = ImageModel.fromJson(json.decode(response.body));
setState(() {
images.add(imageModel);
});
}
Widget build(context) {
return MaterialApp(
home: Scaffold(
body: ImageList(images),
appBar: AppBar(
title: const Text('Lets see some images'),
),
floatingActionButton: FloatingActionButton(
child: const Icon(Icons.add),
onPressed: fetchImage,
),
),
);
}
}
The terminal say this:
The following NetworkImageLoadException was thrown resolving an image
codec: HTTP request failed, statusCode: 403,
https://via.placeholder.com/600/92c952
When the exception was thrown, this was the stack:
#0 NetworkImage._loadAsync (package:flutter/src/painting/_network_image_io.dart:117:9)
Image provider: NetworkImage("https://via.placeholder.com/600/92c952",
scale: 1.0) Image key:
NetworkImage("https://via.placeholder.com/600/92c952", scale: 1.0)
and this is image list class if you need to read it:
import 'package:flutter/material.dart';
import '../models/image_model.dart';
class ImageList extends StatelessWidget {
final List<ImageModel> images;
ImageList(this.images);
Widget build(context) {
return ListView.builder(
itemCount: images.length,
itemBuilder: (context, int index) {
return Image.network(images[index].url);
},
);
}
}
and this is Image Model:
class ImageModel {
late int id;
late String title;
late String url;
ImageModel(this.id, this.title, this.url);
ImageModel.fromJson(Map<String, dynamic> parsedJson) {
id = parsedJson['id'];
title = parsedJson['title'];
url = parsedJson['url'];
}
}
You can use web render, run with --web-renderer html
flutter run -d c --web-renderer html
-d to select device, where c is chrome

Flutter `Screenshot` package: Why isn't the `captureAndSave()` method saving an image to my (Android) device?

Preface:
I'm using the Screenshot package. In this package, there is a method captureAndSave() which saves a widget as an image to a specific location, however, when I call this function, my image is not being saved. Why?
Complete Code Example:
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:path_provider/path_provider.dart';
import 'package:qr_flutter/qr_flutter.dart';
import 'package:screenshot/screenshot.dart';
class QrCodeScreen extends StatefulWidget {
const QrCodeScreen({Key? key}) : super(key: key);
#override
State<QrCodeScreen> createState() => _QrCodeScreenState();
}
class _QrCodeScreenState extends State<QrCodeScreen> {
final _screenshotController = ScreenshotController();
Image? image;
var doesTheImageExist = false;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (image == null)
TextButton(
child: Text("Save QR Code"),
onPressed: () async {
await _captureAndSaveQRCode();
image = await _loadImage();
setState(() {});
},
)
else
image!,
Text("Is the QR Code saved to your device? ${doesTheImageExist}"),
if (image == null)
Screenshot(
controller: _screenshotController,
child: _buildQRImage('_authProvider.user!.uid')),
],
),
);
}
Widget _buildQRImage(String data) {
return QrImage(
data: data,
size: 250.0,
gapless: false,
foregroundColor: Colors.black,
backgroundColor: Colors.white,
);
}
Future<String> get imagePath async {
final directory = (await getApplicationDocumentsDirectory()).path;
return '$directory/qr.png';
}
Future<Image> _loadImage() async {
return imagePath.then((imagePath) => Image.asset(imagePath));
}
Future<void> _captureAndSaveQRCode() async {
final path = await imagePath;
await _screenshotController.captureAndSave(path);
// It always returns false, although I'm saving the file using `captureAndSave` .
doesTheImageExist = File(path).existsSync();
}
}
The Question:
In the code above, when I click on the TextButton() that says "Save QR Code" it then calls _captureAndSaveQRCode() and _loadImage(). Hence my image should successfully be saved to my (Android) phone. However, I get an error:
Unable to load asset: /data/user/0/com.example.qr/app_flutter/qr.png
Full Traceback:
======== Exception caught by image resource service ================================================
The following assertion was thrown resolving an image codec:
Unable to load asset: /data/user/0/com.example.qr/app_flutter/qr.png
When the exception was thrown, this was the stack:
#0 PlatformAssetBundle.load (package:flutter/src/services/asset_bundle.dart:237:7)
<asynchronous suspension>
#1 AssetBundleImageProvider._loadAsync (package:flutter/src/painting/image_provider.dart:675:14)
<asynchronous suspension>
Image provider: AssetImage(bundle: null, name: "/data/user/0/com.example.qr/app_flutter/qr.png")
Image key: AssetBundleImageKey(bundle: PlatformAssetBundle#5986d(), name: "/data/user/0/com.example.qr/app_flutter/qr.png", scale: 1.0)
====================================================================================================
Why isn't my image being saved to the device when calling _captureAndSaveQRCode()?
Side note:
I recently posted an answer (currently in Bounty) with (almost) the same code as in this question which does work correctly, so, what's the difference?
The problem was that I had an empty setState:
onPressed: () async {
await _captureAndSaveQRCode();
image = await _loadImage();
setState(() {});
},
)
So to solve the problem, I removed the setState and also got rid of the _loadImage() function.
And then updated the image variable within the TextButton():
TextButton(
child: Text("Save QR Code"),
onPressed: () async {
await _captureAndSaveQRCode();
setState(() {
doesTheImageExist = true;
image = image;
});
},
)
Complete working example:
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:path_provider/path_provider.dart';
import 'package:qr_flutter/qr_flutter.dart';
import 'package:screenshot/screenshot.dart';
class QrCodeScreen extends StatefulWidget {
const QrCodeScreen({Key? key}) : super(key: key);
#override
State<QrCodeScreen> createState() => _QrCodeScreenState();
}
class _QrCodeScreenState extends State<QrCodeScreen> {
final _screenshotController = ScreenshotController();
Image? image;
var doesTheImageExist = false;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (image == null)
TextButton(
child: Text("Save QR Code"),
onPressed: () async {
await _captureAndSaveQRCode();
setState(() {
doesTheImageExist = true;
image = image;
});
},
)
else
image!,
Row(children: [
Text('hi'),
Text("Is the QR Code saved to your device? ${doesTheImageExist}")
]),
if (image == null)
Screenshot(
controller: _screenshotController,
child: _buildQRImage('_authProvider.user!.uid')),
],
),
);
}
Widget _buildQRImage(String data) {
return QrImage(
data: data,
size: 250.0,
gapless: false,
foregroundColor: Colors.black,
backgroundColor: Colors.white,
);
}
Future<String> get imagePath async {
final directory = (await getApplicationDocumentsDirectory()).path;
return '$directory/qr.png';
}
// Future<Image> _loadImage() async {
// return imagePath.then((imagePath) => Image.asset(imagePath));
// }
Future<void> _captureAndSaveQRCode() async {
final path = await imagePath;
await _screenshotController.captureAndSave(path);
// It always returns false, although I'm saving the file using `captureAndSave` .
doesTheImageExist = File(path).existsSync();
}
}
load image with Image.file(File(imagePath))
Image.Asset is for loading images defined in pubspec.yaml
Edit:
the path in captureAndSave is directory path. it takes another optional argument fileName.
// previous code
// i create new getter for directory
Future<String> get dirPath async {
final directory = (await getApplicationDocumentsDirectory()).path;
return directory;
}
Future<String> get imagePath async {
final directory = await dirPath;
return '$directory/qr.png';
}
Future<Image> _loadImage() async {
return imagePath.then((imagePath) => Image.file(File(imagePath)));
}
Future<void> _captureAndSaveQRCode() async {
final path = await dirPath;
await _screenshotController.captureAndSave(path, fileName: "qr.png");
// It always returns false, although I'm saving the file using `captureAndSave` .
doesTheImageExist = File(path).existsSync();
}
// the rest of the code

Issue while using background_locator plugin while tracking background location in flutter

I'm trying to track location in the background using flutter and to do so I'm using the background_locator plugin. It has been implemented in such a way that there are certain static callback functions that were registered. I've declared a class variable of File type to save the log in the background. The global variable is built at the very beginning of the class.
Issue: While invoking the callback method, the global variable built is becoming null. So though I could see the location log in my console, I couldn't write it to the file as the object is null.
Tries:
I've tried with the exact example provided in their documentation.
I've declared it as non static property and tried to access with the class object.
Tried it out declaring it as static property as well.
Tried building file object with the same path every time needed but it is throwing following issue.
No implementation found for method getApplicationDocumentsDirectory on channel plugins.flutter.io/path_provider
Here is my complete source code for reference.
import 'dart:async';
import 'dart:ffi';
import 'dart:io';
import 'dart:isolate';
import 'dart:math';
import 'dart:ui';
import 'package:background_locator/background_locator.dart';
import 'package:background_locator/location_dto.dart';
import 'package:background_locator/settings/android_settings.dart';
import 'package:background_locator/settings/ios_settings.dart';
import 'package:background_locator/settings/locator_settings.dart';
import 'package:flutter/material.dart';
import 'package:location_permissions/location_permissions.dart';
import 'package:path_provider/path_provider.dart';
import 'package:permission_handler/permission_handler.dart' as ph;
void main() => runApp(const MyApp());
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
ReceivePort port = ReceivePort();
String logStr = '';
bool isRunning = false;
LocationDto? lastLocation;
bool permissionsGranted = false;
static const String isolateName = 'LocatorIsolate';
static int _count = -1;
static File? finalFile;
void requestPermission() async {
var storageStatus = await ph.Permission.storage.status;
if (!storageStatus.isGranted) {
await ph.Permission.storage.request();
}
if (storageStatus.isGranted) {
permissionsGranted = true;
setPrerequisites();
}
setState(() {});
}
static Future<void> init(Map<dynamic, dynamic> params) async {
//TODO change logs
print("***********Init callback handler");
if (params.containsKey('countInit')) {
dynamic tmpCount = params['countInit'];
if (tmpCount is double) {
_count = tmpCount.toInt();
} else if (tmpCount is String) {
_count = int.parse(tmpCount);
} else if (tmpCount is int) {
_count = tmpCount;
} else {
_count = -2;
}
} else {
_count = 0;
}
print("$_count");
await setLogLabel("start");
final SendPort? send = IsolateNameServer.lookupPortByName(isolateName);
send?.send(null);
}
static Future<void> disposeLocationService() async {
await setLogLabel("end");
final SendPort? send = IsolateNameServer.lookupPortByName(isolateName);
send?.send(null);
}
static Future<void> callback(LocationDto locationDto) async {
await setLogPosition(_count, locationDto);
final SendPort? send = IsolateNameServer.lookupPortByName(isolateName);
send?.send(locationDto);
_count++;
}
static Future<void> setLogLabel(String label) async {
final date = DateTime.now();
await _MyAppState().writeToLogFile(
'------------\n$label: ${formatDateLog(date)}\n------------\n');
}
static Future<void> setLogPosition(int count, LocationDto data) async {
final date = DateTime.now();
await _MyAppState().writeToLogFile(
'$count : ${formatDateLog(date)} --> ${formatLog(data)} --- isMocked: ${data.isMocked}\n');
}
static double dp(double val, int places) {
num mod = pow(10.0, places);
return ((val * mod).round().toDouble() / mod);
}
static String formatDateLog(DateTime date) {
return date.hour.toString() +
":" +
date.minute.toString() +
":" +
date.second.toString();
}
static String formatLog(LocationDto locationDto) {
return dp(locationDto.latitude, 4).toString() +
" " +
dp(locationDto.longitude, 4).toString();
}
#override
void initState() {
super.initState();
if (permissionsGranted) {
setPrerequisites();
} else {
requestPermission();
}
}
void setPrerequisites() async {
finalFile = await _getTempLogFile();
if (IsolateNameServer.lookupPortByName(isolateName) != null) {
IsolateNameServer.removePortNameMapping(isolateName);
}
IsolateNameServer.registerPortWithName(port.sendPort, isolateName);
port.listen(
(dynamic data) async {
await updateUI(data);
},
);
initPlatformState();
setState(() {});
}
Future<void> updateUI(LocationDto data) async {
final log = await readLogFile();
await _updateNotificationText(data);
setState(() {
if (data != null) {
lastLocation = data;
}
logStr = log;
});
}
Future<void> _updateNotificationText(LocationDto data) async {
if (data == null) {
return;
}
await BackgroundLocator.updateNotificationText(
title: "new location received",
msg: "${DateTime.now()}",
bigMsg: "${data.latitude}, ${data.longitude}");
}
Future<void> initPlatformState() async {
print('Initializing...');
await BackgroundLocator.initialize();
logStr = await readLogFile();
print('Initialization done');
final _isRunning = await BackgroundLocator.isServiceRunning();
setState(() {
isRunning = _isRunning;
});
print('Running ${isRunning.toString()}');
}
#override
Widget build(BuildContext context) {
final start = SizedBox(
width: double.maxFinite,
child: ElevatedButton(
child: const Text('Start'),
onPressed: () {
_onStart();
},
),
);
final stop = SizedBox(
width: double.maxFinite,
child: ElevatedButton(
child: Text('Stop'),
onPressed: () {
onStop();
},
),
);
final clear = SizedBox(
width: double.maxFinite,
child: ElevatedButton(
child: Text('Clear Log'),
onPressed: () {
clearLogFile();
setState(() {
logStr = '';
});
},
),
);
String msgStatus = "-";
if (isRunning != null) {
if (isRunning) {
msgStatus = 'Is running';
} else {
msgStatus = 'Is not running';
}
}
final status = Text("Status: $msgStatus");
final log = Text(
logStr,
);
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('Flutter background Locator'),
),
body: Container(
width: double.maxFinite,
padding: const EdgeInsets.all(22),
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[start, stop, clear, status, log],
),
),
),
),
);
}
void onStop() async {
await BackgroundLocator.unRegisterLocationUpdate();
final _isRunning = await BackgroundLocator.isServiceRunning();
setState(() {
isRunning = _isRunning;
});
}
void _onStart() async {
if (await _checkLocationPermission()) {
await _startLocator();
final _isRunning = await BackgroundLocator.isServiceRunning();
setState(() {
isRunning = _isRunning;
lastLocation = null;
});
} else {
// show error
}
}
static Future<void> initCallback(Map<dynamic, dynamic> params) async {
await init(params);
}
static Future<void> disposeCallback() async {
await disposeLocationService();
}
Future<void> locationServicecallback(LocationDto locationDto) async {
await callback(locationDto);
}
static Future<void> notificationCallback() async {
print('***notificationCallback');
}
Future<void> writeToLogFile(String log) async {
await finalFile!.writeAsString(log, mode: FileMode.append);
}
Future<String> readLogFile() async {
return finalFile!.readAsString();
}
static Future<File?> _getTempLogFile() async {
File file =
File('${(await getApplicationDocumentsDirectory()).path}/log.txt');
if (file.existsSync()) {
return file;
} else {
file = await file.create(recursive: true);
}
return file;
}
Future<void> clearLogFile() async {
await finalFile!.writeAsString('');
}
Future<bool> _checkLocationPermission() async {
final access = await LocationPermissions().checkPermissionStatus();
switch (access) {
case PermissionStatus.unknown:
case PermissionStatus.denied:
case PermissionStatus.restricted:
final permission = await LocationPermissions().requestPermissions(
permissionLevel: LocationPermissionLevel.locationAlways,
);
if (permission == PermissionStatus.granted) {
return true;
} else {
return false;
}
case PermissionStatus.granted:
return true;
default:
return false;
}
}
Future<void> _startLocator() async {
Map<String, dynamic> data = {'countInit': 1};
return await BackgroundLocator.registerLocationUpdate(
callback,
initCallback: initCallback,
initDataCallback: data,
disposeCallback: disposeCallback,
iosSettings: const IOSSettings(
accuracy: LocationAccuracy.NAVIGATION, distanceFilter: 0),
autoStop: false,
androidSettings: const AndroidSettings(
accuracy: LocationAccuracy.NAVIGATION,
interval: 5,
distanceFilter: 0,
client: LocationClient.google,
androidNotificationSettings: AndroidNotificationSettings(
notificationChannelName: 'Location tracking',
notificationTitle: 'Start Location Tracking',
notificationMsg: 'Track location in background',
notificationBigMsg:
'Background location is on to keep the app up-tp-date with your location. This is required for main features to work properly when the app is not running.',
notificationIconColor: Colors.grey,
notificationTapCallback: notificationCallback,
),
),
);
}
}
Any help/suggestion would be highly appreciated. Thank you!
The callback function not getting called was an issue I faced inthe version 1.6.12.
I fixed the problem by
forking the background_locator repo on github.
cloning the repo to my computer
opened the location_dto.dart file and went to fromJson function.
added json[Keys.ARG_PROVIDER] ?? '' instead
commited and pushed to my forked repository
in pubspec.yaml, I updated my dependency to point to my forked repository as follows:
background_locator:
git:
url: git#github.com:frankvollebregt/background_locator.git
Please follow these two github issues if you find any problem:
https://github.com/rekabhq/background_locator/issues/320
https://github.com/rekabhq/background_locator/issues/301
background_locator dosen't work on latest flutter sdk versions
for me it's worked when I do this steps
Flutter sdk version should be :3.0.1
In pubspec.yaml file change sdk: ">=2.8.0 <3.0.0"
Don't migrate your code to null safety
in gradle-wrapper.properties change gradle version to gradle-6.5
android/build gradle change ext.kotlin_version to '1.4.31'
android/app/build gradle change compileSdkVersion to 31, minSdkVersion to 19 and targetSdkVersion to 30
This is not a problem with the background locator plugin. When the plugin/library is not registered with Flutter Engine, the 'No implementation' error occurs.
You have been attempting to access the path provider methods from within a Background Isolate. Normally, the path provider plugin will be registered with main isolate.
If you want to use it in your background isolate, you must manually register it with the engine.
Follow the steps below and add these two functions to the Init function in location_service_repositary.dart
if (Platform.isAndroid) PathProviderAndroid.registerWith();
if (Platform.isIOS) PathProviderIOS.registerWith();
Have a good day.

Sometimes Provider doesn't trigger after anonymous sign-up in flutter

Hello fellow flutter developers,
I have a bug that's been making my life pretty complicated while developing my own app using Flutter. It goes like this:
User opens app
if they're not signed in, they're redirected to USP page.
If they click next, they're redirected to the sign up page.
Sign-up is provided by Firebase and it's anonymous
If sign-up is successful, a Provider should be triggered and a new page is loaded
The bug is that sometimes the user is sent back to the USP page (meaning their user_id is null) despite no exception between thrown during sign-up. If I force the navigation to the signed-in page, then the user doesn't have an user_id and that's an issue for me.
Any one experienced and fixed the same issue? Below you can see how I built my code, maybe this can help?
This is my main file:
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await SignIn.initializeFirebase();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
getSignInUser() {
return SignIn().user;
}
Widget getMaterialApp() {
return MaterialApp(
title: 'app_title',
home: HomePagePicker(),
onGenerateRoute: RouteGenerator.generateRoute,
debugShowCheckedModeBanner: false,
);
}
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
StreamProvider<MyUser?>.value(value: SignIn().user, initialData: null),
],
child: getMaterialApp(),
);
}
}
class HomePagePicker extends StatefulWidget {
#override
_HomePagePickerState createState() => _HomePagePickerState();
}
class _HomePagePickerState extends State<HomePagePicker> {
#override
Widget build(BuildContext context) {
MyUser? myUser = Provider.of<MyUser?>(context);
if (myUser == null) return IntroScreen(); // this shows the USPs
else {
// this takes you to the signed-in part of the app
return AnotherScreen();
}
}
}
The IntroScreen is a very simple screen with a few USPs and a button to open the registration page. It goes something like
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'login.dart';
class IntroScreen extends StatelessWidget {
static const routeName = '/introScreen';
#override
Widget build(BuildContext context) {
analytics.setScreenName("introScreen");
return Scaffold(
body: AnnotatedRegion<SystemUiOverlayStyle>(
value: SystemUiOverlayStyle(statusBarColor: ThemeConfig.darkPrimary),
child: Column(...), // show the USPs
floatingActionButton: getFloatingButton(context)
);
}
Widget getFloatingButton(BuildContext buildContext) {
return FloatingActionButton(
backgroundColor: ThemeConfig.primary,
foregroundColor: Colors.white,
child: Icon(Icons.arrow_forward),
onPressed: () {
navigateToScreen(MyLogin.routeName, buildContext, null);
},
);
}
// this is in another file normally but putting it here for completeness
navigateToScreen(String routeName, BuildContext context, Object? arguments) {
Navigator.pushNamed(
context,
routeName,
arguments: arguments
);
}
The important bit in the registration page is this
Future<void> finalizeRegistration(String userName, String userToken) async {
await usersCollection.add({'userName': userName, "userToken": userToken});
}
Future<void> registerUser(String userName) {
return SignIn()
.anonymousSignIn(userName)
.timeout(Duration(seconds: 2))
.then((userToken) {
finalizeRegistration(userName, userToken)
.then((value) => Navigator.pop(context));
})
.catchError((error) {
registrationErrorDialog();
});
}
The SignIn class is the following
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:house_party/models/MyUser.dart';
class SignIn {
final FirebaseAuth _auth = FirebaseAuth.instance;
static Future<FirebaseApp> initializeFirebase() async {
FirebaseApp firebaseApp = await Firebase.initializeApp();
return firebaseApp;
}
Stream<MyUser?> get user {
return _auth
.authStateChanges()
.asyncMap(getUser);
}
Future<String> anonymousSignIn(String userName) async {
var authResult = await _auth.signInAnonymously();
return authResult.user!.uid;
}
Future<MyUser?> getUser(User? user) async {
if (user == null) {
return Future.value(null);
}
return FirebaseFirestore.instance
.collection('users')
.where('userToken', isEqualTo: user.uid)
.get()
.then((res) {
if (res.docs.isNotEmpty) {
return MyUser.fromFireStore(res.docs.first.data());
} else {
return null;
}
});
}
}
Finally, I'm using these versions of firebase
firebase_core: ^1.0.0
cloud_firestore: ^1.0.0
firebase_dynamic_links: ^2.0.0
firebase_auth: 1.1.2
firebase_analytics: ^8.1.1
I hope the problem statement is clear enough!
Thanks in advance!
I fixed this problem (or at least I'm not able to reproduce it anymore) by upgrading the firebase libraries as follows
firebase_core: ^1.4.0
cloud_firestore: ^2.4.0
firebase_dynamic_links: ^2.0.7
firebase_auth: 3.0.1
firebase_analytics: ^8.2.0

Flutter Plaid package showing blank screen on iOS

Good day,
I have a flutter app which I have integrated a Plaid flutter package, it works well on android but shows a white screen on iOS.
I have added
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
<key>NSAllowsArbitraryLoadsInWebContent</key>
<true/>
</dict>
in the Info.plist file, but this doesn't seem to make it work.
Below are screenshots
Please I need help on what to do to make iOS platform work.
Here is my configuration
Configuration configuration = Configuration(
plaidPublicKey: '$PLAID_PUBLIC_KEY',
plaidBaseUrl: 'https://cdn.plaid.com/link/v2/stable/link.html',
plaidEnvironment: '$PLAID_ENV',
environmentPlaidPathAccessToken:
'https://sandbox.plaid.com/item/public_token/exchange',
environmentPlaidPathStripeToken:
'https://sandbox.plaid.com/processor/stripe/bank_account_token/create',
// plaidClientId: 'yourPlaidClientId',
// secret: plaidSandbox ? 'yourSecret' : '',
clientName: '$PLAID_CLIENT_NAME',
// webhook: 'Webhook Url',
products: 'auth, transactions',
selectAccount: 'true',
plaidClientId: null);
FlutterPlaidApi flutterPlaidApi = FlutterPlaidApi(configuration);
WidgetsBinding.instance.addPostFrameCallback((_) {
// Add Your Code here.
});
flutterPlaidApi.launch(context, (Result result) async {
// show loader screen when returning back to the app
showLoadingScreen(context, message: 'Processing...');
// send the data to the api
var response = await BankService().linkUserAccountWithSila(
accountName: result.accountName,
publicToken: result.token,
email: 'email#example.com');
final responseJson = json.decode(response.body);
if (response.statusCode >= 200 && response.statusCode <= 299) {
var token = await getToken();
var client = new http.Client();
List<String> urls = [
'getDefaultAccount',
'all',
];
try {
List<http.Response> list =
await Future.wait(urls.map((urlId) => client.get(
'$kBaseUrl/account/$urlId',
headers: {HttpHeaders.authorizationHeader: "Bearer $token"},
)));
if (list[0].statusCode == 200 && list[1].statusCode == 200) {
var defaultAccount = jsonDecode(list[0].body);
var plaidAccounts = jsonDecode(list[1].body);
Provider.of<TransferProvider>(context, listen: false)
.updatePlaidBankAccounts(
plaidAccount:
plaidAccounts['data'] != null ? plaidAccounts['data'] : [],
account: defaultAccount['data'],
);
}
} catch (e) {} finally {
client.close();
}
Navigator.pop(context);
Toast.show('Account linked successfully', context,
duration: Toast.LENGTH_LONG, gravity: Toast.CENTER);
} else {
Toast.show('Something went wrong, please try again later', context,
duration: Toast.LENGTH_LONG, gravity: Toast.CENTER);
// error
}
}, stripeToken: false);
}
Try this code: https://github.com/flutter/flutter/issues/49483
Diclaimer: this is not my code. I am copying it here so that if the original post gets deleted, the source code is still available here. All credits to the original author.
Steps to Reproduce
Register for a free sandbox testing account at Plaid (running the webview in sandbox requires a public_key)
Create new project and add plaid_screen.dart to lib
In plaid_screen.dart assign the public key from Plaid into the queryParameters "key" key
Replace default main.dart content with the setup below
add webview_flutter: ^0.3.19+5 to pubspec.yaml
add <key>io.flutter.embedded_views_preview</key><true/> to ios/Runner/info.plist
Run main.dart
main.dart:
import 'package:bug/plaid_screen.dart';
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
initialRoute: PlaidScreen.id,
routes: {
PlaidScreen.id: (context) => PlaidScreen(),
},
);
}
}
plaid_screen.dart:
(Note: public key must be obtained and pasted below)
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
String authority = 'cdn.plaid.com';
String unencodedPath = '/link/v2/stable/link.html';
Map<String, String> queryParameters = {
"key": "{{PASTE_PUBLIC_KEY}}",
"product": "auth",
"apiVersion": "v2", // set this to "v1" if using the legacy Plaid API
"env": "sandbox",
"clientName": "Test App",
"selectAccount": "true",
};
// documentation: https://plaid.com/docs/#webview-integration
class PlaidScreen extends StatefulWidget {
static const id = 'plaid_screen_id';
#override
_PlaidScreenState createState() => _PlaidScreenState();
}
class _PlaidScreenState extends State<PlaidScreen> {
Uri uri = Uri.https(authority, unencodedPath, queryParameters);
Completer<WebViewController> _controller = Completer<WebViewController>();
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
child: WebView(
javascriptMode: JavascriptMode.unrestricted,
initialUrl: uri.toString(),
onWebViewCreated: (WebViewController webViewController) {
_controller.complete(webViewController);
},
navigationDelegate: (NavigationRequest navRequest) {
debugPrint("NavigationRequest URL: ${navRequest.url}");
if (navRequest.url.contains('plaidlink://')) {
return NavigationDecision.prevent;
}
debugPrint(navRequest.url.toString());
return NavigationDecision.navigate;
},
),
),
);
}
}

Categories

Resources