I'm creating a calendar screen by using TableCalender and Cloud firestore.
I want to set _buildEventList() after assigning a value to selectedEvent at the place where selectedEvent is set, but because _buildEventList() is called first, it will be empty .
But, after i set the value I'm calling setState(){}. Why the screen won't be updated?
final _firestore = Firestore.instance;
FirebaseUser loggedInUser;
// Example holidays
final Map<DateTime, List> _holidays = {
DateTime(2019, 1, 1): ['New Year\'s Day'],
DateTime(2019, 1, 6): ['Epiphany'],
DateTime(2019, 2, 14): ['Valentine\'s Day'],
DateTime(2019, 4, 21): ['Easter Sunday'],
DateTime(2019, 4, 22): ['Easter Monday'],
};
class CalenderScreen extends StatefulWidget {
#override
_CalenderScreenState createState() => _CalenderScreenState();
}
class _CalenderScreenState extends State<CalenderScreen>
with TickerProviderStateMixin {
DateTime _selectedDay;
Map<DateTime, List> _events = {};
Map<DateTime, List> _visibleEvents;
Map<DateTime, List> _visibleHolidays;
List _selectedEvents;
AnimationController _controller;
final _auth = FirebaseAuth.instance;
Widget streamBuilder;
Widget buildEvents;
#override
void initState() {
print("calender");
super.initState();
getCurrentUser();
_selectedDay = DateTime.now();
_selectedEvents = _events[_selectedDay] ?? [];
_visibleEvents = _events;
_visibleHolidays = _holidays;
_controller = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 400),
);
_controller.forward();
}
void getCurrentUser() async {
try {
final user = await _auth.currentUser();
if (user != null) {
loggedInUser = user;
setStreamBuilder();
}
} catch (e) {
print(e);
}
}
void setStreamBuilder() {
setState(() {
streamBuilder = StreamBuilder<QuerySnapshot>(
stream: _firestore
.collection('users')
.document(loggedInUser.uid)
.collection('history')
.snapshots(),
builder: (context, snapshot) {
if (snapshot.data == null)
return Center(child: CircularProgressIndicator());
final historys = snapshot.data.documents;
_events = {};
for (var history in historys) {
DateTime timeStamp = history.data['date'].toDate();
DateTime currentDate =
DateTime(timeStamp.year, timeStamp.month, timeStamp.day);
if (_events.containsKey(currentDate)) {
_events[currentDate].add(history);
} else {
_events[currentDate] = [history];
}
}
print(_events);
DateTime now = DateTime.now();
_selectedDay = DateTime(now.year, now.month, now.day);
//here i set the _selectedEvents
_selectedEvents = _events[_selectedDay] ?? [];
_visibleEvents = _events;
return _buildTableCalendar();
},
);
});
}
void _onDaySelected(DateTime day, List events) {
setState(() {
_selectedDay = day;
_selectedEvents = events;
});
}
void _onVisibleDaysChanged(
DateTime first, DateTime last, CalendarFormat format) {
setState(() {
_visibleEvents = Map.fromEntries(
_events.entries.where(
(entry) =>
entry.key.isAfter(first.subtract(const Duration(days: 1))) &&
entry.key.isBefore(last.add(const Duration(days: 1))),
),
);
_visibleHolidays = Map.fromEntries(
_holidays.entries.where(
(entry) =>
entry.key.isAfter(first.subtract(const Duration(days: 1))) &&
entry.key.isBefore(last.add(const Duration(days: 1))),
),
);
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Color(0xFF232D3D),
body: Column(
mainAxisSize: MainAxisSize.max,
children: <Widget>[
streamBuilder != null
? streamBuilder
: Center(
child: CircularProgressIndicator(),
),
const SizedBox(height: 8.0),
_buildEventList()
],
),
floatingActionButton: FloatingPenButton(),
);
}
Widget _buildTableCalendar() {
return TableCalendar(
~~
);
}
Widget _buildEventList() {
print("bulid event");
return Expanded(
child: ListView(
children: _selectedEvents
.map(
(event) => HistoryCard(
history: HistoryData(
~~
),
)
.toList(),
),
);
}
}
This has been viewed enough that we are all experiencing/searching the same thing. Basically the initial load does not update the state when the streambuilder first loads. If you click and select a day, then it seems to load fine and populate with the events. If anyone can advise why it doesn't rebuild the widget when the stream gets populated by default that would be appreciated.
Related
I tried to do give a command to the flutter application using voice commands and in the application there was a text to voice function. so when i tried to give read command using voice there was a repeat on that function. how to avoid the repeat.
I have tried to terminate the Listening function after getting one input but it dosen't work.
Tried to terminate the Listening function.
but same error
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:newsapp/screens/News_Screen.dart';
import 'package:newsapp/utils/app_colors.dart';
import 'package:newsapp/utils/assets_constants.dart';
import 'package:webfeed/webfeed.dart';
import 'package:flutter_tts/flutter_tts.dart';
import 'package:url_launcher/url_launcher.dart';
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:flutter_speech/flutter_speech.dart';
const languages = const [
// const Language('Francais', 'fr_FR'),
const Language('English', 'en_US'),
const Language('Pусский', 'ru_RU'),
const Language('Italiano', 'it_IT'),
const Language('Español', 'es_ES'),
];
class Language {
final String name;
final String code;
const Language(this.name, this.code);
}
class BBCNews extends StatefulWidget {
#override
_BBCNewsState createState() => _BBCNewsState();
}
class _BBCNewsState extends State<BBCNews> {
late SpeechRecognition _speech;
FlutterTts flutterTts = FlutterTts();
bool _speechRecognitionAvailable = false;
bool _isListening = false;
String transcription = '';
String _currentLocale = 'en_US';
//Language selectedLang = languages.first;
#override
initState() {
super.initState();
activateSpeechRecognizer();
}
void activateSpeechRecognizer() {
//print('_MyAppState.activateSpeechRecognizer... ');
_speech = SpeechRecognition();
_speech.setAvailabilityHandler(onSpeechAvailability);
_speech.setRecognitionStartedHandler(onRecognitionStarted);
_speech.setRecognitionResultHandler(onRecognitionResult);
_speech.setRecognitionCompleteHandler(onRecognitionComplete);
_speech.setErrorHandler(errorHandler);
_speech.activate('en_US').then((res) {
setState(() => _speechRecognitionAvailable = res);
});
}
var client = http.Client();
Future myDevBlog() async {
var response = await client
.get(Uri.parse('http://feeds.bbci.co.uk/news/world/rss.xml'));
var channel = RssFeed.parse(response.body);
final item = channel.items;
return item;
}
Future<void> openFeed(String url) async {
if (await canLaunch(url)) {
await launch(
url,
forceSafariVC: true,
forceWebView: false,
);
return;
}
}
rightIcon() {
return const Icon(
Icons.keyboard_arrow_right,
color: Colors.grey,
size: 30,
);
}
#override
Widget build(BuildContext context) {
var currentIndex;
return Scaffold(
appBar: AppBar(
backgroundColor: AppColors.primaryColor,
//title: const Text('VoiceNews'),
title: Image.asset(
AssetConstants.iconPath,
width: 150,
),
elevation: 10.0,
actions: [
IconButton(
onPressed: _speechRecognitionAvailable && !_isListening
? () => start()
: null,
icon: const Icon(Icons.play_arrow_rounded)),
IconButton(
onPressed: _isListening ? () => stop() : null,
icon: const Icon(Icons.stop_circle))
],
),
body: StreamBuilder(
stream: myDevBlog().asStream(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (context, int index) {
if (transcription.toLowerCase() == "play") {
// _isListening = false;
//stop();
print(transcription);
speak(snapshot.data[1].title);
var currentIndex = 0;
} else if (transcription.toLowerCase() == "next") {
// _isListening = false;
speak(snapshot.data[2].title);
print(transcription);
} else if (transcription.toLowerCase() == "all") {
// _isListening = false;
errorHandler();
//speak("Wrong Input");
//cancel();
}
return Padding(
padding: const EdgeInsets.all(8.0),
child: Card(
child: ListTile(
title: Text(snapshot.data[index].title),
subtitle:
Text(snapshot.data[index].description),
//onTap: () =>
speak(snapshot.data[index].title),
// onTap: () =>
openFeed(snapshot.data[index].link),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
newsScreen(snapshot.data[index].link),
// Pass the arguments as part of the
RouteSettings. The
// DetailScreen reads the arguments
from these settings.
// settings: RouteSettings(
// arguments: todos[index],
// ),
),
);
print(snapshot.data[index].link);
},
onLongPress: () =>
speak(snapshot.data[index].description),
leading: Text((1 + index).toString()),
trailing: rightIcon(),
),
),
);
},
);
} else if (snapshot.hasError ||
snapshot.connectionState == ConnectionState.none) {
return Center(
child: Text(snapshot.error.toString()),
);
}
return const Center(
child: CircularProgressIndicator(),
);
},
),
);
}
// List<CheckedPopupMenuItem<Language>> get
_buildLanguagesWidgets => languages
void start() => _speech.activate(_currentLocale).then((_) {
return _speech.listen().then((result) {
print('_MyAppState.start => result $result');
setState(() {
_isListening = result;
});
});
});
void cancel() =>
_speech.cancel().then((_) => setState(() => _isListening =
false));
void stop() => _speech.stop().then((_) {
setState(() => _isListening = false);
});
void onSpeechAvailability(bool result) =>
setState(() => _speechRecognitionAvailable = result);
// void onCurrentLocale(String locale) {
// // print('_MyAppState.onCurrentLocale... $locale');
// setState(
// () => _currentLocale = languages.firstWhere((l) =>
l.code == locale));
// }
void onRecognitionStarted() {
setState(() => _isListening = true);
}
void onRecognitionResult(String text) {
// print('_MyAppState.onRecognitionResult... $text');
setState(() => transcription = text);
}
void onRecognitionComplete(String text) {
//print('_MyAppState.onRecognitionComplete... $text');
setState(() => _isListening = false);
}
void errorHandler() => activateSpeechRecognizer();
void speak(String text) async {
await flutterTts.speak(text);
await flutterTts.setLanguage("en-US");
await flutterTts.setPitch(1);
await flutterTts.setSpeechRate(0.6);
}
}
I hope you are well, I have a big problem with the flutter audio waveforms package, indeed I want to display the waveforms of my voice files in a folder, but they are not displayed and when I display the value of playerController.bufferData I get a null, please how can I manage this problem, I have already spent days on it.
Sincerely
below are my classes
import 'dart:async';
import 'dart:io';
import 'package:audio_recorder/AudioWaveFormsWidget.dart';
import 'package:audio_waveforms/audio_waveforms.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:path_provider/path_provider.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:record/record.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Audio Recorder',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Audio Recorder'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
//late final PlayerController playerController1;
final record = Record();
int _recordDuration = 0;
Timer? _timer;
RecordState _recordState = RecordState.stop;
StreamSubscription<RecordState>? _recordSub;
final List<File> _audioFile = [];
#override
void initState() {
super.initState();
//Listening record state
_recordSub = record.onStateChanged().listen((recordState) {
setState(() => _recordState = recordState);
});
initCheckPermission();
setState(() {
initFileInDir();
});
}
#override
void dispose() {
_timer?.cancel();
_recordSub?.cancel();
record.dispose();
super.dispose();
}
Future initCheckPermission() async {
final statusMicrophone = await Permission.microphone.request();
final statusStorage = await Permission.storage.request();
if (statusMicrophone != PermissionStatus.granted ||
statusStorage != PermissionStatus.granted) {
await Permission.microphone.request();
await Permission.storage.request();
}
}
void _startTimer() {
_timer?.cancel();
_timer = Timer.periodic(const Duration(seconds: 1), (Timer t) {
setState(() => _recordDuration++);
});
}
Widget _buildText() {
if (_recordState != RecordState.stop) {
return _buildTimer();
}
return const Text("Waiting to record");
}
String _formatNumber(int number) {
String numberStr = number.toString();
if (number < 10) {
numberStr = '0$numberStr';
}
return numberStr;
}
Widget _buildTimer() {
final String minutes = _formatNumber(_recordDuration ~/ 60);
final String seconds = _formatNumber(_recordDuration % 60);
return Text(
'$minutes : $seconds',
style: const TextStyle(color: Colors.red),
);
}
Future<Directory> _getDir() async {
//Check platform and adjust settings
final tempDir = Platform.isAndroid
? await getExternalStorageDirectory()
: await getApplicationDocumentsDirectory();
Directory finalPath;
if (Platform.isAndroid) {
String buildDir =
tempDir!.path.replaceFirst("/data/", "/media/").split("files").first;
buildDir += "AudioRecorder"; // Plus ajout du nom de l'application
return finalPath = await Directory(buildDir).create(recursive: true);
} else {
return finalPath = await Directory(tempDir!.path).create(recursive: true);
}
}
void initFileInDir() async {
Directory finalPath = await _getDir();
var exists = await finalPath.exists();
if (exists) {
finalPath.list(recursive: false).forEach((element) {
File file = File(element.path);
_audioFile.add(file);
});
}
}
Widget _buildStopAndPlayIcon() {
if (_recordState != RecordState.stop) {
return const Icon(Icons.stop);
} else {
return const Icon(Icons.mic);
}
}
Future<void> _start() async {
Directory finalPath = await _getDir();
//Start recording
await record.start(
path: '${finalPath.path}/${DateTime.now().millisecondsSinceEpoch}.m4a',
encoder: AudioEncoder.aacLc, // by default
bitRate: 128000, // by default
samplingRate: 44100);
_recordDuration = 0;
_startTimer();
}
Future<File?> _stop() async {
_timer?.cancel();
_recordDuration = 0;
final path = await record.stop();
File? file;
if (path != null) {
file = File(path);
if (kDebugMode) {
print("--------------------------$path");
}
}
return file;
}
Future<void> _pause() async {
_timer?.cancel();
await record.pause();
}
Future<void> _resume() async {
_startTimer();
await record.resume();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Padding(
padding: const EdgeInsets.all(10.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
LimitedBox(
maxHeight: 650,
child: ListView.builder(
shrinkWrap: true,
itemCount: _audioFile.length,
itemBuilder: (context, index) {
var data = _audioFile[index];
return AudioWaveFormsWidget(path: data);
},
),
),
const Spacer(),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
_buildText(),
ElevatedButton(
onPressed: () async {
if (_recordState == RecordState.stop) {
_start();
} else {
File? newFile = await _stop();
setState((){ _audioFile.add(newFile!);});
}
},
style: ButtonStyle(
elevation: MaterialStateProperty.all(0),
shape: MaterialStateProperty.all(RoundedRectangleBorder(
borderRadius: BorderRadius.circular(100),
side: BorderSide.none))),
child: _buildStopAndPlayIcon(),
)
],
),
],
),
),
// This trailing comma makes auto-formatting nicer for build methods.
);
}
}
import 'dart:io';
import 'package:audio_waveforms/audio_waveforms.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
class AudioWaveFormsWidget extends StatefulWidget {
final File path;
//final PlayerController playerController1;
const AudioWaveFormsWidget({Key? key, required this.path}) : super(key: key);
#override
State<AudioWaveFormsWidget> createState() => _AudioWaveFormsWidgetState();
}
class _AudioWaveFormsWidgetState extends State<AudioWaveFormsWidget> {
late final PlayerController playerController1;
#override
void initState() {
playerController1 = PlayerController()
..addListener(() {
if (mounted) setState(() {});
});
_preparePlayer();
super.initState();
}
void _preparePlayer() async {
bool exist = await widget.path.exists();
if (exist) {
playerController1.preparePlayer(widget.path.path);
}
print("--------------------------------${playerController1.bufferData}");
}
void _playOrPausePlayer(PlayerController controller) async {
debugPrint(
'****************************************${controller.playerState.toString()}');
controller.playerState == PlayerState.playing
? await controller.pausePlayer()
: await controller.startPlayer(finishMode: FinishMode.pause);
}
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10.0),
shape: BoxShape.rectangle,
color: Colors.lightGreenAccent,
),
child: Row(
children: [
IconButton(
onPressed: () {
_playOrPausePlayer(playerController1);
},
icon: playerController1.playerState == PlayerState.playing
? const Icon(Icons.pause)
: const Icon(Icons.play_arrow)),
AudioFileWaveforms(
density: 1.0,
enableSeekGesture: true,
size: const Size(100.0, 70.0),
playerController: playerController1),
],
)),
);
}
}
if you're trying to record, try setting the bitRate to 48000 (worked for me on iOS when it wasn't showing the waveforms when recording).
final RecorderController recorderController = RecorderController()
..androidEncoder = AndroidEncoder.aac
..androidOutputFormat = AndroidOutputFormat.mpeg4
..iosEncoder = IosEncoder.kAudioFormatMPEG4AAC
..sampleRate = 44100
..bitRate = 48000;
I'm trying to implement Flutter InApp purchases on Consumables but I keep getting the following - error you already own this item when I try to buy again.
I want the user to buy over and over again.
This is happening on android.
I'm using in_app_purchase: ^0.3.4+5
I used the code in official docs for the plugin -
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:in_app_purchase/in_app_purchase.dart';
import 'package:in_app_purchase/store_kit_wrappers.dart';
import 'consumable_store.dart';
void main() {
// For play billing library 2.0 on Android, it is mandatory to call
// [enablePendingPurchases](https://developer.android.com/reference/com/android/billingclient/api/BillingClient.Builder.html#enablependingpurchases)
// as part of initializing the app.
InAppPurchaseConnection.enablePendingPurchases();
runApp(MyApp());
}
// Original code link: https://github.com/flutter/plugins/blob/master/packages/in_app_purchase/example/lib/main.dart
const bool kAutoConsume = true;
const String _kConsumableId = '';
const String _kSubscriptionId = '';
const List<String> _kProductIds = <String>[
_kConsumableId,
'noadforfifteendays',
_kSubscriptionId
];
// TODO: Please Add your android product ID here
const List<String> _kAndroidProductIds = <String>[
''
];
//Example
//const List<String> _kAndroidProductIds = <String>[
// 'ADD_YOUR_ANDROID_PRODUCT_ID_1',
// 'ADD_YOUR_ANDROID_PRODUCT_ID_2',
// 'ADD_YOUR_ANDROID_PRODUCT_ID_3'
//];
// TODO: Please Add your iOS product ID here
const List<String> _kiOSProductIds = <String>[
''
];
//Example
//const List<String> _kiOSProductIds = <String>[
// 'ADD_YOUR_IOS_PRODUCT_ID_1',
// 'ADD_YOUR_IOS_PRODUCT_ID_2',
// 'ADD_YOUR_IOS_PRODUCT_ID_3'
//];
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
final InAppPurchaseConnection _connection = InAppPurchaseConnection.instance;
StreamSubscription<List<PurchaseDetails>> _subscription;
List<String> _notFoundIds = [];
List<ProductDetails> _products = [];
List<PurchaseDetails> _purchases = [];
List<String> _consumables = [];
bool _isAvailable = false;
bool _purchasePending = false;
bool _loading = true;
String _queryProductError;
#override
void initState() {
DateTime currentDate = DateTime.now();
DateTime noADDate;
var fiftyDaysFromNow = currentDate.add(new Duration(days: 50));
print('${fiftyDaysFromNow.month} - ${fiftyDaysFromNow.day} - ${fiftyDaysFromNow.year} ${fiftyDaysFromNow.hour}:${fiftyDaysFromNow.minute}');
Stream purchaseUpdated =
InAppPurchaseConnection.instance.purchaseUpdatedStream;
_subscription = purchaseUpdated.listen((purchaseDetailsList) {
_listenToPurchaseUpdated(purchaseDetailsList);
}, onDone: () {
_subscription.cancel();
}, onError: (error) {
// handle error here.
});
initStoreInfo();
super.initState();
}
Future<void> initStoreInfo() async {
final bool isAvailable = await _connection.isAvailable();
if (!isAvailable) {
setState(() {
_isAvailable = isAvailable;
_products = [];
_purchases = [];
_notFoundIds = [];
_consumables = [];
_purchasePending = false;
_loading = false;
});
return;
}
ProductDetailsResponse productDetailResponse =
await _connection.queryProductDetails(Platform.isIOS ? _kiOSProductIds.toSet() : _kAndroidProductIds.toSet());//_kProductIds.toSet());
if (productDetailResponse.error != null) {
setState(() {
_queryProductError = productDetailResponse.error.message;
_isAvailable = isAvailable;
_products = productDetailResponse.productDetails;
_purchases = [];
_notFoundIds = productDetailResponse.notFoundIDs;
_consumables = [];
_purchasePending = false;
_loading = false;
});
return;
}
if (productDetailResponse.productDetails.isEmpty) {
setState(() {
_queryProductError = null;
_isAvailable = isAvailable;
_products = productDetailResponse.productDetails;
_purchases = [];
_notFoundIds = productDetailResponse.notFoundIDs;
_consumables = [];
_purchasePending = false;
_loading = false;
});
return;
}
final QueryPurchaseDetailsResponse purchaseResponse =
await _connection.queryPastPurchases();
if (purchaseResponse.error != null) {
// handle query past purchase error..
}
final List<PurchaseDetails> verifiedPurchases = [];
for (PurchaseDetails purchase in purchaseResponse.pastPurchases) {
if (await _verifyPurchase(purchase)) {
verifiedPurchases.add(purchase);
}
}
List<String> consumables = await ConsumableStore.load();
setState(() {
_isAvailable = isAvailable;
_products = productDetailResponse.productDetails;
_purchases = verifiedPurchases;
_notFoundIds = productDetailResponse.notFoundIDs;
_consumables = consumables;
_purchasePending = false;
_loading = false;
});
}
#override
void dispose() {
_subscription.cancel();
super.dispose();
}
#override
Widget build(BuildContext context) {
List<Widget> stack = [];
if (_queryProductError == null) {
stack.add(
ListView(
children: [
_buildConnectionCheckTile(),
_buildProductList(),
_buildConsumableBox(),
],
),
);
} else {
stack.add(Center(
child: Text(_queryProductError),
));
}
if (_purchasePending) {
stack.add(
Stack(
children: [
Opacity(
opacity: 0.3,
child: const ModalBarrier(dismissible: false, color: Colors.grey),
),
Center(
child: CircularProgressIndicator(),
),
],
),
);
}
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('IAP Example'),
),
body: Stack(
children: stack,
),
),
);
}
Card _buildConnectionCheckTile() {
if (_loading) {
return Card(child: ListTile(title: const Text('Trying to connect...')));
}
final Widget storeHeader = ListTile(
leading: Icon(_isAvailable ? Icons.check : Icons.block,
color: _isAvailable ? Colors.green : ThemeData.light().errorColor),
title: Text(
'The store is ' + (_isAvailable ? 'available' : 'unavailable') + '.'),
);
final List<Widget> children = <Widget>[storeHeader];
if (!_isAvailable) {
children.addAll([
Divider(),
ListTile(
title: Text('Not connected',
style: TextStyle(color: ThemeData.light().errorColor)),
subtitle: const Text(
'Unable to connect to the payments processor. Has this app been configured correctly? See the example README for instructions.'),
),
]);
}
return Card(child: Column(children: children));
}
Card _buildProductList() {
if (_loading) {
return Card(
child: (ListTile(
leading: CircularProgressIndicator(),
title: Text('Fetching products...'))));
}
if (!_isAvailable) {
return Card();
}
final ListTile productHeader = ListTile(title: Text('Products for Sale'));
List<ListTile> productList = <ListTile>[];
if (_notFoundIds.isNotEmpty) {
productList.add(ListTile(
title: Text('[${_notFoundIds.join(", ")}] not found',
style: TextStyle(color: ThemeData.light().errorColor)),
subtitle: Text(
'This app needs special configuration to run. Please see example/README.md for instructions.')));
}
// This loading previous purchases code is just a demo. Please do not use this as it is.
// In your app you should always verify the purchase data using the `verificationData` inside the [PurchaseDetails] object before trusting it.
// We recommend that you use your own server to verity the purchase data.
Map<String, PurchaseDetails> purchases =
Map.fromEntries(_purchases.map((PurchaseDetails purchase) {
if (purchase.pendingCompletePurchase) {
InAppPurchaseConnection.instance.completePurchase(purchase);
}
return MapEntry<String, PurchaseDetails>(purchase.productID, purchase);
}));
productList.addAll(_products.map(
(ProductDetails productDetails) {
PurchaseDetails previousPurchase = purchases[productDetails.id];
return ListTile(
title: Text(
productDetails.title,
),
subtitle: Text(
productDetails.description,
),
trailing: previousPurchase != null
? Icon(Icons.check)
: FlatButton(
child: Text(productDetails.price),
color: Colors.green[800],
textColor: Colors.white,
onPressed: () {
PurchaseParam purchaseParam = PurchaseParam(
productDetails: productDetails,
applicationUserName: null,
sandboxTesting: false);
if (productDetails.id == _kConsumableId) {
_connection.buyConsumable(
purchaseParam: purchaseParam,
autoConsume: kAutoConsume || Platform.isIOS);
} else {
_connection.buyNonConsumable(
purchaseParam: purchaseParam);
}
},
));
},
));
return Card(
child:
Column(children: <Widget>[productHeader, Divider()] + productList));
}
Card _buildConsumableBox() {
if (_loading) {
return Card(
child: (ListTile(
leading: CircularProgressIndicator(),
title: Text('Fetching consumables...'))));
}
if (!_isAvailable || _notFoundIds.contains(_kConsumableId)) {
return Card();
}
final ListTile consumableHeader =
ListTile(title: Text('Purchased consumables'));
final List<Widget> tokens = _consumables.map((String id) {
return GridTile(
child: IconButton(
icon: Icon(
Icons.stars,
size: 42.0,
color: Colors.orange,
),
splashColor: Colors.yellowAccent,
onPressed: () => consume(id),
),
);
}).toList();
return Card(
child: Column(children: <Widget>[
consumableHeader,
Divider(),
GridView.count(
crossAxisCount: 5,
children: tokens,
shrinkWrap: true,
padding: EdgeInsets.all(16.0),
)
]));
}
Future<void> consume(String id) async {
print('consume id is $id');
await ConsumableStore.consume(id);
final List<String> consumables = await ConsumableStore.load();
setState(() {
_consumables = consumables;
});
}
void showPendingUI() {
setState(() {
_purchasePending = true;
});
}
void deliverProduct(PurchaseDetails purchaseDetails) async {
print('deliverProduct'); // Last
// IMPORTANT!! Always verify a purchase purchase details before delivering the product.
if (purchaseDetails.productID == _kConsumableId) {
await ConsumableStore.save(purchaseDetails.purchaseID);
List<String> consumables = await ConsumableStore.load();
setState(() {
_purchasePending = false;
_consumables = consumables;
});
} else {
setState(() {
_purchases.add(purchaseDetails);
_purchasePending = false;
});
}
}
void handleError(IAPError error) {
setState(() {
_purchasePending = false;
});
}
Future<bool> _verifyPurchase(PurchaseDetails purchaseDetails) {
// IMPORTANT!! Always verify a purchase before delivering the product.
// For the purpose of an example, we directly return true.
print('_verifyPurchase');
return Future<bool>.value(true);
}
void _handleInvalidPurchase(PurchaseDetails purchaseDetails) {
// handle invalid purchase here if _verifyPurchase` failed.
print('_handleInvalidPurchase');
}
void _listenToPurchaseUpdated(List<PurchaseDetails> purchaseDetailsList) {
print('_listenToPurchaseUpdated');
purchaseDetailsList.forEach((PurchaseDetails purchaseDetails) async {
if (purchaseDetails.status == PurchaseStatus.pending) {
showPendingUI();
} else {
if (purchaseDetails.status == PurchaseStatus.error) {
handleError(purchaseDetails.error);
} else if (purchaseDetails.status == PurchaseStatus.purchased) {
bool valid = await _verifyPurchase(purchaseDetails);
if (valid) {
deliverProduct(purchaseDetails);
} else {
_handleInvalidPurchase(purchaseDetails);
return;
}
}
if (Platform.isAndroid) {
if (!kAutoConsume && purchaseDetails.productID == _kConsumableId) {
await InAppPurchaseConnection.instance
.consumePurchase(purchaseDetails);
}
}
if (purchaseDetails.pendingCompletePurchase) {
await InAppPurchaseConnection.instance
.completePurchase(purchaseDetails);
}
}
});
}
}
How do I solve this?
If you want to test only with non-consumable product, then you can refund the test purchase and use it again later.
Make sure your products are consumable, and also being purchased using the buyConsumable call.
I just started learning Flutter. And I faced one big problem which is bad scrolling in complex list. Let's say we have 5 different item type in our ListView and some item type must display images and it's infinite scroll. I read a lot articles and posts about ListView for Flutter and all the things I've seen are simple list with text. How can I make smooth scroll?
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:english_words/english_words.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Startup Name Generator',
home: new RandomWords(),
);
}
}
class RandomWords extends StatefulWidget {
#override
RandomWordsState createState() => new RandomWordsState();
}
class RandomWordsState extends State<RandomWords> {
final List<WordPair> _suggestions = <WordPair>[];
final TextStyle _biggerFont = const TextStyle(fontSize: 18.0);
List<int> items = List.generate(10, (i) => i);
ScrollController _scrollController = new ScrollController();
bool isPerformingRequest = false;
#override
void initState() {
super.initState();
_scrollController.addListener(() {
if (_scrollController.position.pixels ==
_scrollController.position.maxScrollExtent) {
_getMoreData();
}
});
}
#override
void dispose() {
_scrollController.dispose();
super.dispose();
}
_getMoreData() async {
if (!isPerformingRequest) {
setState(() => isPerformingRequest = true);
List<int> newEntries = await fakeRequest(
items.length, items.length + 10); //returns empty list
if (newEntries.isEmpty) {
double edge = 50.0;
double offsetFromBottom = _scrollController.position.maxScrollExtent -
_scrollController.position.pixels;
if (offsetFromBottom < edge) {
_scrollController.animateTo(
_scrollController.offset - (edge - offsetFromBottom),
duration: new Duration(milliseconds: 500),
curve: Curves.easeOut);
}
}
setState(() {
items.addAll(newEntries);
isPerformingRequest = false;
});
}
}
Future<List<int>> fakeRequest(int from, int to) async {
return Future.delayed(Duration(seconds: 1), () {
return List.generate(to - from, (i) => i + from);
});
}
Widget _buildProgressIndicator() {
return new Padding(
padding: const EdgeInsets.all(8.0),
child: new Center(
child: new Opacity(
opacity: isPerformingRequest ? 1.0 : 0.0,
child: new CircularProgressIndicator(),
),
),
);
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: AppBar(
title: Text(""),
),
body: ListView.builder(
itemCount: items.length + 1,
itemBuilder: (context, index) {
if (index == items.length) {
return _buildProgressIndicator();
} else {
if (index % 5 == 0) {
return Image.network(
"http://sanctum-inle-resort.com/wp-content/uploads/2015/11/Sanctum_Inl_Resort_Myanmar_Flowers_Frangipani.jpg",
height: 200.0,
);
} else if (index.isOdd) {
return Container(
padding: EdgeInsets.all(16.0),
child: ListTile(
leading: Icon(Icons.person),
title: Text('This is title'),
),
);
} else {
return ListTile(title: new Text("Number $index"));
}
}
},
controller: _scrollController,
),
);
}
}
I think the issue is in the fact that you request the data async when the user is already to the end of the list and has no more items and then it's performing a fake request which loads the data but this takes a sec so that's why it's studdering
_scrollController.position.maxScrollExtent
This gets the end of the scroll list so it starts loading at the point the user is at the end of the list you could change this value with an offset so it starts loading earlier
To test this you could try changing:
if (_scrollController.position.pixels ==
_scrollController.position.maxScrollExtent) {
To:
if (_scrollController.position.pixels ==
(_scrollController.position.maxScrollExtent - 50)) {
And
Future<List<int>> fakeRequest(int from, int to) async {
return Future.delayed(Duration(seconds: 1), () {
return List.generate(to - from, (i) => i + from);
});
}
To:
List<int> fakeRequest(int from, int to) {
return List.generate(to - from, (i) => i + from);
}
I'm trying to capture an image from camera and save it in cache(i.e., Directory.systemTemp available from dart.io package). It is working fine in debug mode. But when I build the release apk and install, it is not working. This is the code:
import 'dart:async';
import 'dart:io';
import 'package:camera/camera.dart';
import 'package:flutter/material.dart';
import 'package:path_provider/path_provider.dart';
class CameraExampleHome extends StatefulWidget {
#override
_CameraExampleHomeState createState() {
return new _CameraExampleHomeState();
}
}
/// Returns a suitable camera icon for [direction].
IconData getCameraLensIcon(CameraLensDirection direction) {
switch (direction) {
case CameraLensDirection.back:
return Icons.camera_rear;
case CameraLensDirection.front:
return Icons.camera_front;
case CameraLensDirection.external:
return Icons.camera;
}
throw new ArgumentError('Unknown lens direction');
}
void logError(String code, String message) =>
print('Error: $code\nError Message: $message');
class _CameraExampleHomeState extends State<CameraExampleHome> {
CameraController controller;
String imagePath;
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
Future<List<CameraDescription>> getCameras() async {
return await availableCameras();
}
#override
Widget build(BuildContext context) {
return new Scaffold(
key: _scaffoldKey,
appBar: new AppBar(
title: const Text('Camera example'),
),
body: FutureBuilder(
future: getCameras(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none:
case ConnectionState.waiting:
return new Text('loading...');
default:
if (snapshot.hasError)
return new Text('Error: ${snapshot.error}');
else {
if (snapshot.hasData)
return createCameraView(context, snapshot);
else
return new Text('Null');
}
}
},
),
);
}
/// Display the preview from the camera (or a message if the preview is not available).
Widget _cameraPreviewWidget() {
if (controller == null || !controller.value.isInitialized) {
return const Text(
'Tap a camera',
style: const TextStyle(
color: Colors.white,
fontSize: 24.0,
fontWeight: FontWeight.w900,
),
);
} else {
return new AspectRatio(
aspectRatio: controller.value.aspectRatio,
child: new CameraPreview(controller),
);
}
}
/// Display the thumbnail of the captured image or video
/// Display the control bar with buttons to take pictures and record videos.
Widget _captureControlRowWidget() {
return new Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
mainAxisSize: MainAxisSize.max,
children: <Widget>[
new IconButton(
icon: const Icon(Icons.camera_alt),
color: Colors.blue,
onPressed: controller != null &&
controller.value.isInitialized &&
!controller.value.isRecordingVideo
? onTakePictureButtonPressed
: null,
),
],
);
}
/// Display a row of toggle to select the camera (or a message if no camera is available).
Widget _cameraTogglesRowWidget(var cameras) {
final List<Widget> toggles = <Widget>[];
if (cameras.isEmpty) {
return const Text('No camera found');
} else {
for (CameraDescription cameraDescription in cameras) {
toggles.add(
new SizedBox(
width: 90.0,
child: new RadioListTile<CameraDescription>(
title:
new Icon(getCameraLensIcon(cameraDescription.lensDirection)),
groupValue: controller?.description,
value: cameraDescription,
onChanged: controller != null && controller.value.isRecordingVideo
? null
: onNewCameraSelected,
),
),
);
}
}
return new Row(children: toggles);
}
String timestamp() => new DateTime.now().millisecondsSinceEpoch.toString();
void showInSnackBar(String message) {
_scaffoldKey.currentState
.showSnackBar(new SnackBar(content: new Text(message)));
}
void onNewCameraSelected(CameraDescription cameraDescription) async {
if (controller != null) {
await controller.dispose();
}
controller = new CameraController(cameraDescription, ResolutionPreset.high);
// If the controller is updated then update the UI.
controller.addListener(() {
if (mounted) setState(() {});
if (controller.value.hasError) {
showInSnackBar('Camera error ${controller.value.errorDescription}');
}
});
try {
await controller.initialize();
} on CameraException catch (e) {
_showCameraException(e);
}
if (mounted) {
setState(() {});
}
}
void onTakePictureButtonPressed() {
takePicture().then((String filePath) {
if (mounted) {
setState(() {
imagePath = filePath;
// videoController?.dispose();
// videoController = null;
});
if (filePath != null) showInSnackBar('Picture saved to $filePath');
}
});
}
Future<String> takePicture() async {
if (!controller.value.isInitialized) {
showInSnackBar('Error: select a camera first.');
return null;
}
//final Directory extDir = await getApplicationDocumentsDirectory();
final Directory extDir = Directory.systemTemp;
final String dirPath = '${extDir.path}/Pictures/flutter_test';
await new Directory(dirPath).create(recursive: true);
final String filePath = '$dirPath/${timestamp()}.jpg';
if (controller.value.isTakingPicture) {
// A capture is already pending, do nothing.
return null;
}
try {
await controller.takePicture(filePath);
} on CameraException catch (e) {
_showCameraException(e);
return null;
}
return filePath;
}
void _showCameraException(CameraException e) {
logError(e.code, e.description);
showInSnackBar('Error: ${e.code}\n${e.description}');
}
Widget createCameraView(BuildContext context, AsyncSnapshot snapshot) {
return new Column(
children: <Widget>[
new Expanded(
child: new Container(
width: MediaQuery.of(context).size.width,
child: _cameraPreviewWidget(),
),
),
_captureControlRowWidget(),
new Padding(
padding: const EdgeInsets.all(5.0),
child: new Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
_cameraTogglesRowWidget(snapshot.data),
//_thumbnailWidget(),
],
),
),
],
);
}
}
class CameraApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
home: new CameraExampleHome(),
);
}
}
List<CameraDescription> cameras;
void main() {
// Fetch the available cameras before initializing the app.
runApp(new CameraApp());
}
I've tried setting the directory to await getApplicationDocumentsDirectory() like this:
final Directory extDir = await getApplicationDocumentsDirectory();
//final Directory extDir = Directory.systemTemp;
final String dirPath = '${extDir.path}/Pictures/flutter_test';
await new Directory(dirPath).create(recursive: true);
final String filePath = '$dirPath/${timestamp()}.jpg';
Then the pictures are being saved both in debug mode and the release apk. But the pictures are lost once I relaunch the app. I want them to accessible even after the app is relaunched(that is the main reason why I want to use cache).
So what's going wrong when I try to save to cache in release build of the app?
use the path_provider package instead to get the temp path https://pub.dartlang.org/packages/path_provider
Directory tempDir = await getTemporaryDirectory();
String tempPath = tempDir.path;
Directory.systemTemp is only supposed to be used on the server/console.