Control ESP32 via Flutter BLE - flutter

I am using this library (https://pub.dev/packages/flutter_blue/example) to control my ESP32 (Arduino). I have two buttons that work perfectly, one to turn off the led and one to turn on the led. My problem is that I want to receive data that the arduino sends (notify). I don’t know how to do it.
main.dart
// Copyright 2017, Paul DeMarco.
// 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:convert' show utf8;
import 'package:flutter/material.dart';
import 'package:flutter_blue/flutter_blue.dart';
import 'package:minertti/widgets.dart';
void main() {
runApp(FlutterBlueApp());
}
class FlutterBlueApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
color: Colors.lightBlue,
home: StreamBuilder<BluetoothState>(
stream: FlutterBlue.instance.state,
initialData: BluetoothState.unknown,
builder: (c, snapshot) {
final state = snapshot.data;
if (state == BluetoothState.on) {
return FindDevicesScreen();
}
return BluetoothOffScreen(state: state);
}),
);
}
}
class BluetoothOffScreen extends StatelessWidget {
const BluetoothOffScreen({Key? key, this.state}) : super(key: key);
final BluetoothState? state;
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.lightBlue,
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Icon(
Icons.bluetooth_disabled,
size: 200.0,
color: Colors.white54,
),
Text(
'Bluetooth Adapter is ${state != null ? state.toString().substring(15) : 'not available'}.',
style: Theme.of(context)
.primaryTextTheme
.subtitle1
?.copyWith(color: Colors.white),
),
],
),
),
);
}
}
class FindDevicesScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Find Devices'),
),
body: RefreshIndicator(
onRefresh: () =>
FlutterBlue.instance.startScan(timeout: Duration(seconds: 4)),
child: SingleChildScrollView(
child: Column(
children: <Widget>[
StreamBuilder<List<BluetoothDevice>>(
stream: Stream.periodic(Duration(seconds: 2))
.asyncMap((_) => FlutterBlue.instance.connectedDevices),
initialData: [],
builder: (c, snapshot) => Column(
children: snapshot.data!
.map((d) => ListTile(
title: Text(d.name),
subtitle: Text(d.id.toString()),
trailing: StreamBuilder<BluetoothDeviceState>(
stream: d.state,
initialData: BluetoothDeviceState.disconnected,
builder: (c, snapshot) {
if (snapshot.data ==
BluetoothDeviceState.connected) {
return RaisedButton(
child: Text('OPEN'),
onPressed: () => Navigator.of(context).push(
MaterialPageRoute(
builder: (context) =>
DeviceScreen(device: d))),
);
}
return Text(snapshot.data.toString());
},
),
))
.toList(),
),
),
StreamBuilder<List<ScanResult>>(
stream: FlutterBlue.instance.scanResults,
initialData: [],
builder: (c, snapshot) => Column(
children: snapshot.data!
.map(
(r) => ScanResultTile(
result: r,
onTap: () => Navigator.of(context)
.push(MaterialPageRoute(builder: (context) {
r.device.connect();
return DeviceScreen(device: r.device);
})),
),
)
.toList(),
),
),
],
),
),
),
floatingActionButton: StreamBuilder<bool>(
stream: FlutterBlue.instance.isScanning,
initialData: false,
builder: (c, snapshot) {
if (snapshot.data!) {
return FloatingActionButton(
child: Icon(Icons.stop),
onPressed: () => FlutterBlue.instance.stopScan(),
backgroundColor: Colors.red,
);
} else {
return FloatingActionButton(
child: Icon(Icons.search),
onPressed: () => FlutterBlue.instance
.startScan(timeout: Duration(seconds: 4)));
}
},
),
);
}
}
class DeviceScreen extends StatelessWidget {
const DeviceScreen({Key? key, required this.device}) : super(key: key);
final BluetoothDevice device;
List<Widget> _buildServiceTiles(List<BluetoothService> services) {
return services
//just show the last service
.sublist(services.length - 1)
.map(
(s) => ServiceTile(
service: s,
characteristicTiles: s.characteristics
.map(
(c) => CharacteristicTile(
characteristic: c,
//turnOff: () => c.read(),
turnOff: () => c.write(utf8.encode("0")),
turnOn: () => c.write(utf8.encode("1")),
extraButton: () => c.write(utf8.encode("2")),
onNotificationPressed: () async {
await c.setNotifyValue(!c.isNotifying);
await c.read();
},
descriptorTiles: c.descriptors
.map(
(d) => DescriptorTile(
descriptor: d,
//turnOff: () => d.read(),
//turnOn: () => d.read(),
),
)
.toList(),
),
)
.toList(),
),
)
.toList();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(device.name),
actions: <Widget>[
StreamBuilder<BluetoothDeviceState>(
stream: device.state,
initialData: BluetoothDeviceState.connecting,
builder: (c, snapshot) {
VoidCallback? onPressed;
String text;
switch (snapshot.data) {
case BluetoothDeviceState.connected:
onPressed = () => device.disconnect();
text = 'DISCONNECT';
break;
case BluetoothDeviceState.disconnected:
onPressed = () => device.connect();
text = 'CONNECT';
break;
default:
onPressed = null;
text = snapshot.data.toString().substring(21).toUpperCase();
break;
}
return FlatButton(
onPressed: onPressed,
child: Text(
text,
style: Theme.of(context)
.primaryTextTheme
.button
?.copyWith(color: Colors.white),
));
},
)
],
),
body: SingleChildScrollView(
child: Column(
children: <Widget>[
StreamBuilder<BluetoothDeviceState>(
stream: device.state,
initialData: BluetoothDeviceState.connecting,
builder: (c, snapshot) => ListTile(
leading: (snapshot.data == BluetoothDeviceState.connected)
? Icon(Icons.bluetooth_connected)
: Icon(Icons.bluetooth_disabled),
title: Text(
'Device is ${snapshot.data.toString().split('.')[1]}.'),
subtitle: Text('${device.id}'),
trailing: StreamBuilder<bool>(
stream: device.isDiscoveringServices,
initialData: false,
builder: (c, snapshot) => IndexedStack(
index: snapshot.data! ? 1 : 0,
children: <Widget>[
IconButton(
icon: Icon(Icons.refresh),
onPressed: () => device.discoverServices(),
),
IconButton(
icon: SizedBox(
child: CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation(Colors.grey),
),
width: 18.0,
height: 18.0,
),
onPressed: null,
)
],
),
),
),
),
StreamBuilder<int>(
stream: device.mtu,
initialData: 0,
builder: (c, snapshot) => ListTile(
title: Text('MTU Size'),
subtitle: Text('${snapshot.data} bytes'),
trailing: IconButton(
icon: Icon(Icons.edit),
onPressed: () => device.requestMtu(223),
),
),
),
StreamBuilder<List<BluetoothService>>(
stream: device.services,
initialData: [],
builder: (c, snapshot) {
return Column(
children: _buildServiceTiles(snapshot.data!),
);
},
),
],
),
),
);
}
}
device.dart
// Copyright 2017, Paul DeMarco.
// 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:convert' show utf8;
import 'package:flutter/material.dart';
import 'package:flutter_blue/flutter_blue.dart';
class ScanResultTile extends StatelessWidget {
const ScanResultTile({Key? key, required this.result, this.onTap})
: super(key: key);
final ScanResult result;
final VoidCallback? onTap;
Widget _buildTitle(BuildContext context) {
if (result.device.name.length > 0) {
return Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
result.device.name,
overflow: TextOverflow.ellipsis,
),
Text(
result.device.id.toString(),
style: Theme.of(context).textTheme.caption,
)
],
);
} else {
return Text(result.device.id.toString());
}
}
Widget _buildAdvRow(BuildContext context, String title, String value) {
return Padding(
padding: EdgeInsets.symmetric(horizontal: 16.0, vertical: 4.0),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(title, style: Theme.of(context).textTheme.caption),
SizedBox(
width: 12.0,
),
Expanded(
child: Text(
value,
style: Theme.of(context)
.textTheme
.caption
?.apply(color: Colors.black),
softWrap: true,
),
),
],
),
);
}
String getNiceHexArray(List<int> bytes) {
return '[${bytes.map((i) => i.toRadixString(16).padLeft(2, '0')).join(', ')}]'
.toUpperCase();
}
String getNiceManufacturerData(Map<int, List<int>> data) {
if (data.isEmpty) {
return 'N/A';
}
List<String> res = [];
data.forEach((id, bytes) {
res.add(
'${id.toRadixString(16).toUpperCase()}: ${getNiceHexArray(bytes)}');
});
return res.join(', ');
}
String getNiceServiceData(Map<String, List<int>> data) {
if (data.isEmpty) {
return 'N/A';
}
List<String> res = [];
data.forEach((id, bytes) {
res.add('${id.toUpperCase()}: ${getNiceHexArray(bytes)}');
});
return res.join(', ');
}
#override
Widget build(BuildContext context) {
return ExpansionTile(
title: _buildTitle(context),
leading: Text(result.rssi.toString()),
trailing: RaisedButton(
child: Text('CONNECT'),
color: Colors.black,
textColor: Colors.white,
onPressed: (result.advertisementData.connectable) ? onTap : null,
),
children: <Widget>[
_buildAdvRow(
context, 'Complete Local Name', result.advertisementData.localName),
_buildAdvRow(context, 'Tx Power Level',
'${result.advertisementData.txPowerLevel ?? 'N/A'}'),
_buildAdvRow(context, 'Manufacturer Data',
getNiceManufacturerData(result.advertisementData.manufacturerData)),
_buildAdvRow(
context,
'Service UUIDs',
(result.advertisementData.serviceUuids.isNotEmpty)
? result.advertisementData.serviceUuids.join(', ').toUpperCase()
: 'N/A'),
_buildAdvRow(context, 'Service Data',
getNiceServiceData(result.advertisementData.serviceData)),
],
);
}
}
class ServiceTile extends StatelessWidget {
final BluetoothService service;
final List<CharacteristicTile> characteristicTiles;
const ServiceTile(
{Key? key, required this.service, required this.characteristicTiles})
: super(key: key);
#override
Widget build(BuildContext context) {
if (characteristicTiles.length > 0) {
return ExpansionTile(
title: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text('Service'),
Text('0x${service.uuid.toString().toUpperCase().substring(4, 8)}',
style: Theme.of(context).textTheme.bodyText1?.copyWith(
color: Theme.of(context).textTheme.caption?.color))
],
),
children: characteristicTiles,
);
} else {
return ListTile(
title: Text('Service'),
subtitle:
Text('0x${service.uuid.toString().toUpperCase().substring(4, 8)}'),
);
}
}
}
class CharacteristicTile extends StatelessWidget {
final BluetoothCharacteristic characteristic;
final List<DescriptorTile> descriptorTiles;
final VoidCallback? turnOff;
final VoidCallback? extraButton;
final VoidCallback? turnOn;
final VoidCallback? onNotificationPressed;
const CharacteristicTile(
{Key? key,
required this.characteristic,
required this.descriptorTiles,
this.turnOff,
this.extraButton,
this.turnOn,
this.onNotificationPressed})
: super(key: key);
#override
Widget build(BuildContext context) {
return StreamBuilder<List<int>>(
stream: characteristic.value,
initialData: characteristic.lastValue,
builder: (c, snapshot) {
final value = snapshot.data;
return ExpansionTile(
title: ListTile(
title: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text('Characteristic'),
Text(
'0x${characteristic.uuid.toString().toUpperCase().substring(4, 8)}',
style: Theme.of(context).textTheme.bodyText1?.copyWith(
color: Theme.of(context).textTheme.caption?.color))
],
),
subtitle: Text(value.toString()),
contentPadding: EdgeInsets.all(0.0),
),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
IconButton(
icon: Icon(
Icons.lightbulb_outline,
color: Theme.of(context).iconTheme.color?.withOpacity(0.5),
),
onPressed: turnOff,
),
IconButton(
icon: Icon(Icons.lightbulb,
color: Theme.of(context).iconTheme.color?.withOpacity(0.5)),
onPressed: turnOn,
),
//iconButton for add
IconButton(
icon: Icon(
Icons.add_circle,
color: Theme.of(context).iconTheme.color?.withOpacity(0.5),
),
onPressed: extraButton,
),
IconButton(
icon: Icon(
characteristic.isNotifying
? Icons.sync_disabled
: Icons.sync,
color: Theme.of(context).iconTheme.color?.withOpacity(0.5)),
onPressed: onNotificationPressed,
)
],
),
children: descriptorTiles,
);
},
);
}
}
class DescriptorTile extends StatelessWidget {
final BluetoothDescriptor descriptor;
final VoidCallback? turnOff;
final VoidCallback? turnOn;
final VoidCallback? extraButton;
const DescriptorTile(
{Key? key,
required this.descriptor,
this.turnOff,
this.extraButton,
this.turnOn})
: super(key: key);
#override
Widget build(BuildContext context) {
return ListTile(
title: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text('Descriptor'),
Text('0x${descriptor.uuid.toString().toUpperCase().substring(4, 8)}',
style: Theme.of(context)
.textTheme
.bodyText1
?.copyWith(color: Theme.of(context).textTheme.caption?.color))
],
),
subtitle: StreamBuilder<List<int>>(
stream: descriptor.value,
initialData: descriptor.lastValue,
builder: (c, snapshot) => Text(snapshot.data.toString()),
),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
IconButton(
icon: Icon(
Icons.lightbulb_outline,
color: Theme.of(context).iconTheme.color?.withOpacity(0.5),
),
onPressed: turnOff,
),
IconButton(
icon: Icon(
Icons.lightbulb,
color: Theme.of(context).iconTheme.color?.withOpacity(0.5),
),
onPressed: turnOn,
),
IconButton(
icon: Icon(
Icons.add_circle,
color: Theme.of(context).iconTheme.color?.withOpacity(0.5),
),
onPressed: extraButton,
)
],
),
);
}
}
class AdapterStateTile extends StatelessWidget {
const AdapterStateTile({Key? key, required this.state}) : super(key: key);
final BluetoothState state;
#override
Widget build(BuildContext context) {
return Container(
color: Colors.redAccent,
child: ListTile(
title: Text(
'Bluetooth adapter is ${state.toString().substring(15)}',
style: Theme.of(context).primaryTextTheme.subtitle1,
),
trailing: Icon(
Icons.error,
color: Theme.of(context).primaryTextTheme.subtitle1?.color,
),
),
);
}
}
ESP32 Code:
#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>
BLECharacteristic *pCharacteristic;
bool deviceConnected = false;
float txValue = 0;
const int readPin = 32; // Use GPIO number. See ESP32 board pinouts
const int LED = 2; // Could be different depending on the dev board. I used the DOIT ESP32 dev board.
//std::string rxValue; // Could also make this a global var to access it in loop()
// See the following for generating UUIDs:
// https://www.uuidgenerator.net/
#define SERVICE_UUID "6E400001-B5A3-F393-E0A9-E50E24DCCA9E" // UART service UUID
#define CHARACTERISTIC_UUID_RX "6E400002-B5A3-F393-E0A9-E50E24DCCA9E"
#define CHARACTERISTIC_UUID_TX "6E400003-B5A3-F393-E0A9-E50E24DCCA9E"
class MyServerCallbacks: public BLEServerCallbacks {
void onConnect(BLEServer* pServer) {
deviceConnected = true;
};
void onDisconnect(BLEServer* pServer) {
deviceConnected = false;
}
};
class MyCallbacks: public BLECharacteristicCallbacks {
void onWrite(BLECharacteristic *pCharacteristic) {
std::string rxValue = pCharacteristic->getValue();
if (rxValue.length() > 0) {
Serial.println("*********");
Serial.print("Received Value: ");
for (int i = 0; i < rxValue.length(); i++) {
Serial.print(rxValue[i]);
}
Serial.println();
// Do stuff based on the command received from the app
if (rxValue.find("1") != -1) {
Serial.println("Turning ON!");
digitalWrite(LED, HIGH);
}
else if (rxValue.find("0") != -1) {
Serial.println("Turning OFF!");
digitalWrite(LED, LOW);
}
Serial.println();
Serial.println("*********");
}
}
};
void setup() {
Serial.begin(115200);
pinMode(LED, OUTPUT);
// Create the BLE Device
BLEDevice::init("ESP32"); // Give it a name
// Create the BLE Server
BLEServer *pServer = BLEDevice::createServer();
pServer->setCallbacks(new MyServerCallbacks());
// Create the BLE Service
BLEService *pService = pServer->createService(SERVICE_UUID);
// Create a BLE Characteristic
pCharacteristic = pService->createCharacteristic(
CHARACTERISTIC_UUID_TX,
BLECharacteristic::PROPERTY_NOTIFY
);
pCharacteristic->addDescriptor(new BLE2902());
BLECharacteristic *pCharacteristic = pService->createCharacteristic(
CHARACTERISTIC_UUID_RX,
BLECharacteristic::PROPERTY_WRITE
);
pCharacteristic->setCallbacks(new MyCallbacks());
// Start the service
pService->start();
// Start advertising
pServer->getAdvertising()->start();
Serial.println("Waiting a client connection to notify...");
}
void loop() {
if (deviceConnected) {
// Fabricate some arbitrary junk for now...
txValue = analogRead(readPin) / 3.456; // This could be an actual sensor reading!
// Let's convert the value to a char array:
char txString[8]; // make sure this is big enuffz
dtostrf(txValue, 1, 2, txString); // float_val, min_width, digits_after_decimal, char_buffer
// pCharacteristic->setValue(&txValue, 1); // To send the integer value
// pCharacteristic->setValue("Hello!"); // Sending a test message
pCharacteristic->setValue(txString);
pCharacteristic->notify(); // Send the value to the app!
Serial.print("*** Sent Value: ");
Serial.print(txString);
Serial.println(" ***");
// You can add the rxValue checks down here instead
// if you set "rxValue" as a global var at the top!
// Note you will have to delete "std::string" declaration
// of "rxValue" in the callback function.
// if (rxValue.find("A") != -1) {
// Serial.println("Turning ON!");
// digitalWrite(LED, HIGH);
// }
// else if (rxValue.find("B") != -1) {
// Serial.println("Turning OFF!");
// digitalWrite(LED, LOW);
// }
}
delay(1000);
}
The app correctly receives the information, but in a strange code that I don't understand. How do I convert it to letters and numbers?
In device.dart you can see that there is a part that says: "subtitle: Text(value.toString())"
That is the one that sends the data, but I don't understand it.
...
builder: (c, snapshot) {
final value = snapshot.data;
return ExpansionTile(
title: ListTile(
title: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text('Characteristic'),
Text(
'0x${characteristic.uuid.toString().toUpperCase().substring(4, 8)}',
style: Theme.of(context).textTheme.bodyText1?.copyWith(
color: Theme.of(context).textTheme.caption?.color))
],
),
subtitle: Text(value.toString()),
...
Arduino sends: 98.96 and I receive this data from value.toString(): [57, 56, 46, 57, 54]

Try using String.fromCharCodes to convert the received data ([57, 56, 46, 57, 54]) to 98.96.

Refer to this utf8 char table: https://cloud.netlifyusercontent.com/assets/344dbf88-fdf9-42bb-adb4-46f01eedd629/02572bcf-76c4-420a-90ac-65a0c8c71eae/iso-8859-5-php-orange.png
You are receiving Uint8List. Decode that data into string then parse it in double.
Try this:
String decodedData = utf8.decode(receivedData);
where receivedData -> Uint8List
Now in decodedData you will get the required string that you can parse into other data types.

Related

Flutter Web Unexpected Null Value

enter image description here I get Unexpected Null value error when switching from Home page to Vehicle Add page. I couldn't understand what caused this.
I tried to run it in debug mode, but it was very confusing in debug mode, I couldn't understand it.
home.dart page
import 'package:Car_Price_App/Pages/category_add.dart';
import 'package:Car_Price_App/Pages/vehicle_add.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import '../services/auth_service.dart';
class Home extends StatefulWidget {
const Home({super.key});
#override
State<Home> createState() => _HomeState();
}
class _HomeState extends State<Home> {
#override
Widget build(BuildContext context) {
final user = FirebaseAuth.instance.currentUser;
final email = user?.email;
return Scaffold(
appBar: AppBar(
title: Center(child: Text("Sıfır Araçlar ${email?.toUpperCase()}")),
leading: const Icon(Icons.car_rental),
actions: [
IconButton(onPressed: () {
Navigator.push(context, MaterialPageRoute(builder: (context)=> CategoryAdd(gelenEmail: email.toString(),)));
print("giden $email");
}, icon: const Icon(Icons.category)),
IconButton(onPressed: () {
Navigator.push(context, MaterialPageRoute(builder: (context)=> VehicleAdd()));
}, icon: const Icon(Icons.add)),
IconButton(onPressed: () {}, icon: const Icon(Icons.update)),
IconButton(onPressed: () {}, icon: const Icon(Icons.delete)),
],
),
body: Center(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text("Giriş Yapıldı $email"),
const SizedBox(height: 10,),
ElevatedButton(onPressed: (){
AuthService().signOut();
}, child: const Text("Çıkış Yap",),),
],
),
),
);
}
}
vehicle_add.dart page
import 'dart:io';
import 'dart:math';
import 'dart:core';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:currency_text_input_formatter/currency_text_input_formatter.dart';
import 'package:file_picker/file_picker.dart';
import 'package:firebase_storage/firebase_storage.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import '../models/trade_mark_model.dart';
import '../models/type_of_vehicle_model.dart';
import '../models/vehicle_body_type_model.dart';
import '../models/vehicle_model.dart';
import '../models/vehicle_year_model.dart';
class VehicleAdd extends StatefulWidget {
const VehicleAdd({Key? key}) : super(key: key);
#override
State<VehicleAdd> createState() => _VehicleAddState();
}
class _VehicleAddState extends State<VehicleAdd> {
GlobalKey? aracEkleKey = GlobalKey<FormState>();
var db = FirebaseFirestore.instance;
TextEditingController vehicleProperties = TextEditingController();
TextEditingController vehiclePrice = TextEditingController();
String? vasitaTipi = "";
String? kasaTipi = "";
String? marka = "";
String? model = "";
String? yil = "";
PlatformFile? pickedFile;
UploadTask? uploadTask;
Future selectFile() async {
final result = await FilePicker.platform.pickFiles();
if (result == null) return;
setState(() {
pickedFile = result.files.first;
});
}
Future uploadFile() async {
final path = 'aracResimleri/${pickedFile?.name}';
final file = File(pickedFile!.path!);
final ref = FirebaseStorage.instance.ref().child(path);
setState(() {
uploadTask = ref.putFile(file);
});
final snapshot = await uploadTask?.whenComplete(() {});
final urlDownload = await snapshot?.ref.getDownloadURL();
print('Download Link : $urlDownload');
setState(() {
uploadTask = null;
});
}
Stream<List<TypeOfVehicleModel>> readVehicleType() => db.collection("Vasıtalar").snapshots().map((snapshot) => snapshot.docs.map((doc) => TypeOfVehicleModel.fromJson(doc.data())).toList());
Stream<List<VehicleBodyTypeModel>> readBodyType() => db.collection("KasaTipleri").snapshots().map((snapshot) => snapshot.docs.map((doc) => VehicleBodyTypeModel.fromJson(doc.data())).toList());
Stream<List<TradeMarkModel>> readTradeMark() => db.collection("Markalar").snapshots().map((snapshot) => snapshot.docs.map((doc) => TradeMarkModel.fromJson(doc.data())).toList());
Stream<List<VehicleModel>> readVehicleModel() => db.collection("Modeller").snapshots().map((snapshot) => snapshot.docs.map((doc) => VehicleModel.fromJson(doc.data())).toList());
Stream<List<VehicleYearModel>> readVehicleYear() => db.collection("Yıllar").snapshots().map((snapshot) => snapshot.docs.map((doc) => VehicleYearModel.fromJson(doc.data())).toList());
late int _key;
_collapse() {
int? newKey;
do {
_key = Random().nextInt(10000);
} while (newKey == _key);
}
#override
void initState() {
super.initState();
_collapse();
}
#override
Widget build(BuildContext context) {
var screenInfo = MediaQuery.of(context);
final double screenWidth = screenInfo.size.width;
final double screenHeight = screenInfo.size.height;
return Scaffold(
appBar: AppBar(
title: const Text("Araç Ekle"),
),
body: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 30, vertical: 20),
child: Center(
child: Form(
autovalidateMode: AutovalidateMode.onUserInteraction,
key: aracEkleKey,
child: SizedBox(
width: screenWidth / 2,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
FutureBuilder<List<TypeOfVehicleModel>>(
future: readVehicleType().first,
builder: (context, snapshot) {
if (snapshot.hasError) {
return const Text("Something went wrong");
} else if (snapshot.hasData) {
final typeOfVehicle = snapshot.data!;
return ExpansionTile(
key: Key(_key.toString()),
initiallyExpanded: false,
leading: const Icon(Icons.merge_type),
title: const Text("Vasıta Seçiniz"),
children: typeOfVehicle.map(buildTov).toList(),
);
} else {
return const Center(
child: Padding(
padding: EdgeInsets.all(8.0),
child: CircularProgressIndicator(),
),
);
}
},
), // Vasıta Tipi
Text("$vasitaTipi"),
Text("$kasaTipi"),
Text("$marka"),
Text("$model"),
Text("$yil"),
FutureBuilder<List<VehicleBodyTypeModel>>(
future: readBodyType().first,
builder: (context, snapshot) {
if (snapshot.hasError) {
return const Text("Something went wrong");
} else if (snapshot.hasData) {
final bodyType = snapshot.data!;
return ExpansionTile(
key: Key(_key.toString()),
initiallyExpanded: false,
leading: const Icon(Icons.type_specimen),
title: const Text("Kasa Tipi Seçiniz"),
children: bodyType.map(buildBodyType).toList(),
);
} else {
return const Center(
child: Padding(
padding: EdgeInsets.all(8.0),
child: CircularProgressIndicator(),
),
);
}
},
), // Kasatipi
FutureBuilder<List<TradeMarkModel>>(
future: readTradeMark().first,
builder: (context, snapshot) {
if (snapshot.hasError) {
return const Text("Something went wrong");
} else if (snapshot.hasData) {
final tradeMarka = snapshot.data!;
return ExpansionTile(
key: Key(_key.toString()),
initiallyExpanded: false,
leading: const Icon(Icons.branding_watermark),
title: const Text("Marka Seçiniz"),
children: tradeMarka.map(buildTradeMark).toList(),
);
} else {
return const Center(
child: Padding(
padding: EdgeInsets.all(8.0),
child: CircularProgressIndicator(),
),
);
}
},
), //Markalar
FutureBuilder<List<VehicleModel>>(
future: readVehicleModel().first,
builder: (context, snapshot) {
if (snapshot.hasError) {
return const Text("Something went wrong");
} else if (snapshot.hasData) {
final model = snapshot.data!;
return ExpansionTile(
key: Key(_key.toString()),
initiallyExpanded: false,
leading: const Icon(Icons.time_to_leave),
title: const Text("Model Seçiniz"),
children: model.map(buildModel).toList(),
);
} else {
return const Center(
child: Padding(
padding: EdgeInsets.all(8.0),
child: CircularProgressIndicator(),
),
);
}
},
), //Modeller
FutureBuilder<List<VehicleYearModel>>(
future: readVehicleYear().first,
builder: (context, snapshot) {
if (snapshot.hasError) {
return const Text("Something went wrong");
} else if (snapshot.hasData) {
final year = snapshot.data!;
return ExpansionTile(
key: Key(_key.toString()),
initiallyExpanded: false,
leading: const Icon(Icons.calendar_month),
title: const Text(
"Yıl Seçiniz",
),
children: year.map(buildYear).toList(),
);
} else {
return const Center(
child: Padding(
padding: EdgeInsets.all(8.0),
child: CircularProgressIndicator(),
),
);
}
},
), //Yıllar
Padding(
padding: const EdgeInsets.all(8.0),
child: TextFormField(
controller: vehicleProperties,
decoration: const InputDecoration(
labelText: "Araç Özellikleri Giriniz",
suffixText: "Araç Özellikleri Giriniz",
border: OutlineInputBorder(),
),
minLines: 1,
maxLines: screenHeight.toInt(),
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: TextFormField(
keyboardType: TextInputType.number,
inputFormatters: <TextInputFormatter>[
// for below version 2 use this
FilteringTextInputFormatter.allow(RegExp(
r'\d',
)),
FilteringTextInputFormatter.digitsOnly,
CurrencyTextInputFormatter(
symbol: '₺',
name: "TL",
)
],
controller: vehiclePrice,
decoration: const InputDecoration(
labelText: "Araç Fiyat Giriniz",
suffixText: "Araç Fiyat Giriniz",
border: OutlineInputBorder(),
),
minLines: 1,
maxLines: screenHeight.toInt(),
),
),
Expanded(
child: Image.file(
File(pickedFile!.path!),
width: double.infinity,
fit: BoxFit.cover,
),
),
ElevatedButton.icon(
icon: const Icon(Icons.image),
onPressed: () {
selectFile();
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: const Text("Araç Resmi Seç"),
content: Text(pickedFile!.name),
actions: [
TextButton(
onPressed: () async {
uploadFile();
},
child: const Center(
child: Text(
"Galeri'den Seç",
style: TextStyle(color: Colors.black, fontSize: 20),
))),
],
);
});
},
label: const Text("Araç Resimi Ekle"),
),
const SizedBox(
height: 30,
),
StreamBuilder<TaskSnapshot>(
stream: uploadTask!.snapshotEvents,
builder: (context, snapshot) {
if (snapshot.hasData) {
final data = snapshot.data!;
double progress = data.bytesTransferred / data.totalBytes;
return SizedBox(
height: 50,
child: Stack(
fit: StackFit.expand,
children: [
LinearProgressIndicator(
value: progress,
color: Colors.green,
backgroundColor: Colors.grey,
),
Center(
child: Text(
'${(100 * progress).roundToDouble()} %',
style: const TextStyle(color: Colors.white),
),
)
],
),
);
} else {
return const SizedBox(
height: 50,
);
}
},
)
],
),
),
),
),
),
),
// Fab Buton visible olacak doldurulması geren herşey dolduğunda gözükecek
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
floatingActionButton: FloatingActionButton(
child: const Icon(Icons.add),
onPressed: () {
// bool kontrolSonucu = aracEkleKey.currentState!.validate();
},
),
);
}
Widget buildTov(TypeOfVehicleModel typeOfVehicleModel) => ListTile(
onTap: () {
setState(() {
_collapse();
vasitaTipi = typeOfVehicleModel.typeOfVehicle;
});
if (kDebugMode) {
print("$vasitaTipi");
}
},
leading: const Icon(
Icons.stars_outlined,
size: 10,
),
title: Text(typeOfVehicleModel.typeOfVehicle.toString()),
); //Araç vasıta tipi
Widget buildBodyType(VehicleBodyTypeModel vehicleBodyTypeModel) => ListTile(
onTap: () {
setState(() {
_collapse();
kasaTipi = vehicleBodyTypeModel.vehicleBodyType;
});
if (kDebugMode) {
print(vehicleBodyTypeModel.vehicleBodyType);
}
},
leading: const Icon(
Icons.stars_outlined,
size: 10,
),
title: Text(vehicleBodyTypeModel.vehicleBodyType.toString()),
); // Araç kasa tipi
Widget buildTradeMark(TradeMarkModel tradeMarkModel) => ListTile(
onTap: () {
setState(() {
_collapse();
marka = tradeMarkModel.tradeMarkName;
});
if (kDebugMode) {
print(tradeMarkModel.tradeMarkName);
}
},
leading: const Icon(
Icons.stars_outlined,
size: 10,
),
title: Text(tradeMarkModel.tradeMarkName.toString()),
); // Araç MArka
Widget buildModel(VehicleModel vehicleModel) => ListTile(
onTap: () {
setState(() {
_collapse();
model = vehicleModel.modelName;
});
if (kDebugMode) {
print(vehicleModel.modelName);
}
},
leading: const Icon(
Icons.stars_outlined,
size: 10,
),
title: Text(vehicleModel.modelName.toString()),
); // Araç Modeli
Widget buildYear(VehicleYearModel vehicleYearModel) => ListTile(
onTap: () {
setState(() {
_collapse();
yil = vehicleYearModel.year;
});
if (kDebugMode) {
print(vehicleYearModel.year);
}
},
leading: const Icon(
Icons.stars_outlined,
size: 10,
),
title: Text(vehicleYearModel.year.toString()),
); // Araç Yılı
Widget buildProgress() => StreamBuilder<TaskSnapshot>(
stream: uploadTask?.snapshotEvents,
builder: (context, snapshot) {
if (snapshot.hasData) {
final data = snapshot.data!;
double progress = data.bytesTransferred / data.totalBytes;
return SizedBox(
height: 50,
child: Stack(
fit: StackFit.expand,
children: [
LinearProgressIndicator(
value: progress,
color: Colors.green,
backgroundColor: Colors.grey,
),
Center(
child: Text(
'${(100 * progress).roundToDouble()} %',
style: const TextStyle(color: Colors.white),
),
)
],
),
);
} else {
return const SizedBox(
height: 50,
);
}
},
);
}
I don't know which variable is causing the problem. I'm inexperienced in flutter.
The error message says on which line the error is. See
That means it's on line 297. If I'm not mistaken that is the line:
child: Image.file(File(pickedFile!.path!),
So pickedFile is null

Problem with write data over flutter_blue

I have a Question to Flutter with Bluetooth.
I Use the Flutter_blue package https://pub.dev/packages/flutter_blue
How I can write data external over a Future or a Void.
I use the code of the documentation Example of Flutter_blue.
Just I will extend this with my own void to write data every 5 seconds.
Here is my code that I want to control
import 'dart:convert';
import 'package:flutter_blue/flutter_blue.dart';
class SecondBlue {
BluetoothCharacteristic targetCharacteristic;
SecondBlue(this.targetCharacteristic);
writeData(String data) async {
if (targetCharacteristic == null) return;
List<int> bytes = utf8.encode(data);
await targetCharacteristic.write(bytes);
}
}
i would like after the first bluetooth part provides the connection
write data about this part at any time.
here is the code of flutter_blue to connect the device.
The main Part:
// Copyright 2017, Paul DeMarco.
// 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:math';
import 'package:flutter/material.dart';
import 'package:flutter_blue/flutter_blue.dart';
import 'package:flutter_blue_example/widgets.dart';
void main() {
runApp(FlutterBlueApp());
}
class FlutterBlueApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
color: Colors.lightBlue,
home: StreamBuilder<BluetoothState>(
stream: FlutterBlue.instance.state,
initialData: BluetoothState.unknown,
builder: (c, snapshot) {
final state = snapshot.data;
if (state == BluetoothState.on) {
return FindDevicesScreen();
}
return BluetoothOffScreen(state: state);
}),
);
}
}
class BluetoothOffScreen extends StatelessWidget {
const BluetoothOffScreen({Key? key, this.state}) : super(key: key);
final BluetoothState? state;
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.lightBlue,
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Icon(
Icons.bluetooth_disabled,
size: 200.0,
color: Colors.white54,
),
Text(
'Bluetooth Adapter is ${state != null ? state.toString().substring(15) : 'not available'}.',
style: Theme.of(context)
.primaryTextTheme
.subhead
?.copyWith(color: Colors.white),
),
],
),
),
);
}
}
class FindDevicesScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Find Devices'),
),
body: RefreshIndicator(
onRefresh: () =>
FlutterBlue.instance.startScan(timeout: Duration(seconds: 4)),
child: SingleChildScrollView(
child: Column(
children: <Widget>[
StreamBuilder<List<BluetoothDevice>>(
stream: Stream.periodic(Duration(seconds: 2))
.asyncMap((_) => FlutterBlue.instance.connectedDevices),
initialData: [],
builder: (c, snapshot) => Column(
children: snapshot.data!
.map((d) => ListTile(
title: Text(d.name),
subtitle: Text(d.id.toString()),
trailing: StreamBuilder<BluetoothDeviceState>(
stream: d.state,
initialData: BluetoothDeviceState.disconnected,
builder: (c, snapshot) {
if (snapshot.data ==
BluetoothDeviceState.connected) {
return RaisedButton(
child: Text('OPEN'),
onPressed: () => Navigator.of(context).push(
MaterialPageRoute(
builder: (context) =>
DeviceScreen(device: d))),
);
}
return Text(snapshot.data.toString());
},
),
))
.toList(),
),
),
StreamBuilder<List<ScanResult>>(
stream: FlutterBlue.instance.scanResults,
initialData: [],
builder: (c, snapshot) => Column(
children: snapshot.data!
.map(
(r) => ScanResultTile(
result: r,
onTap: () => Navigator.of(context)
.push(MaterialPageRoute(builder: (context) {
r.device.connect();
return DeviceScreen(device: r.device);
})),
),
)
.toList(),
),
),
],
),
),
),
floatingActionButton: StreamBuilder<bool>(
stream: FlutterBlue.instance.isScanning,
initialData: false,
builder: (c, snapshot) {
if (snapshot.data!) {
return FloatingActionButton(
child: Icon(Icons.stop),
onPressed: () => FlutterBlue.instance.stopScan(),
backgroundColor: Colors.red,
);
} else {
return FloatingActionButton(
child: Icon(Icons.search),
onPressed: () => FlutterBlue.instance
.startScan(timeout: Duration(seconds: 4)));
}
},
),
);
}
}
class DeviceScreen extends StatelessWidget {
const DeviceScreen({Key? key, required this.device}) : super(key: key);
final BluetoothDevice device;
List<int> _getRandomBytes() {
final math = Random();
return [
math.nextInt(255),
math.nextInt(255),
math.nextInt(255),
math.nextInt(255)
];
}
List<Widget> _buildServiceTiles(List<BluetoothService> services) {
return services
.map(
(s) => ServiceTile(
service: s,
characteristicTiles: s.characteristics
.map(
(c) => CharacteristicTile(
characteristic: c,
onReadPressed: () => c.read(),
onWritePressed: () async {
await c.write(_getRandomBytes(), withoutResponse: true);
await c.read();
},
onNotificationPressed: () async {
await c.setNotifyValue(!c.isNotifying);
await c.read();
},
descriptorTiles: c.descriptors
.map(
(d) => DescriptorTile(
descriptor: d,
onReadPressed: () => d.read(),
onWritePressed: () => d.write(_getRandomBytes()),
),
)
.toList(),
),
)
.toList(),
),
)
.toList();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(device.name),
actions: <Widget>[
StreamBuilder<BluetoothDeviceState>(
stream: device.state,
initialData: BluetoothDeviceState.connecting,
builder: (c, snapshot) {
VoidCallback? onPressed;
String text;
switch (snapshot.data) {
case BluetoothDeviceState.connected:
onPressed = () => device.disconnect();
text = 'DISCONNECT';
break;
case BluetoothDeviceState.disconnected:
onPressed = () => device.connect();
text = 'CONNECT';
break;
default:
onPressed = null;
text = snapshot.data.toString().substring(21).toUpperCase();
break;
}
return FlatButton(
onPressed: onPressed,
child: Text(
text,
style: Theme.of(context)
.primaryTextTheme
.button
?.copyWith(color: Colors.white),
));
},
)
],
),
body: SingleChildScrollView(
child: Column(
children: <Widget>[
StreamBuilder<BluetoothDeviceState>(
stream: device.state,
initialData: BluetoothDeviceState.connecting,
builder: (c, snapshot) => ListTile(
leading: (snapshot.data == BluetoothDeviceState.connected)
? Icon(Icons.bluetooth_connected)
: Icon(Icons.bluetooth_disabled),
title: Text(
'Device is ${snapshot.data.toString().split('.')[1]}.'),
subtitle: Text('${device.id}'),
trailing: StreamBuilder<bool>(
stream: device.isDiscoveringServices,
initialData: false,
builder: (c, snapshot) => IndexedStack(
index: snapshot.data! ? 1 : 0,
children: <Widget>[
IconButton(
icon: Icon(Icons.refresh),
onPressed: () => device.discoverServices(),
),
IconButton(
icon: SizedBox(
child: CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation(Colors.grey),
),
width: 18.0,
height: 18.0,
),
onPressed: null,
)
],
),
),
),
),
StreamBuilder<int>(
stream: device.mtu,
initialData: 0,
builder: (c, snapshot) => ListTile(
title: Text('MTU Size'),
subtitle: Text('${snapshot.data} bytes'),
trailing: IconButton(
icon: Icon(Icons.edit),
onPressed: () => device.requestMtu(223),
),
),
),
StreamBuilder<List<BluetoothService>>(
stream: device.services,
initialData: [],
builder: (c, snapshot) {
return Column(
children: _buildServiceTiles(snapshot.data!),
);
},
),
],
),
),
);
}
}
and here the second Part:
// Copyright 2017, Paul DeMarco.
// All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'package:flutter/material.dart';
import 'package:flutter_blue/flutter_blue.dart';
class ScanResultTile extends StatelessWidget {
const ScanResultTile({Key? key, required this.result, this.onTap})
: super(key: key);
final ScanResult result;
final VoidCallback? onTap;
Widget _buildTitle(BuildContext context) {
if (result.device.name.length > 0) {
return Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
result.device.name,
overflow: TextOverflow.ellipsis,
),
Text(
result.device.id.toString(),
style: Theme.of(context).textTheme.caption,
)
],
);
} else {
return Text(result.device.id.toString());
}
}
Widget _buildAdvRow(BuildContext context, String title, String value) {
return Padding(
padding: EdgeInsets.symmetric(horizontal: 16.0, vertical: 4.0),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(title, style: Theme.of(context).textTheme.caption),
SizedBox(
width: 12.0,
),
Expanded(
child: Text(
value,
style: Theme.of(context)
.textTheme
.caption
?.apply(color: Colors.black),
softWrap: true,
),
),
],
),
);
}
String getNiceHexArray(List<int> bytes) {
return '[${bytes.map((i) => i.toRadixString(16).padLeft(2, '0')).join(', ')}]'
.toUpperCase();
}
String getNiceManufacturerData(Map<int, List<int>> data) {
if (data.isEmpty) {
return 'N/A';
}
List<String> res = [];
data.forEach((id, bytes) {
res.add(
'${id.toRadixString(16).toUpperCase()}: ${getNiceHexArray(bytes)}');
});
return res.join(', ');
}
String getNiceServiceData(Map<String, List<int>> data) {
if (data.isEmpty) {
return 'N/A';
}
List<String> res = [];
data.forEach((id, bytes) {
res.add('${id.toUpperCase()}: ${getNiceHexArray(bytes)}');
});
return res.join(', ');
}
#override
Widget build(BuildContext context) {
return ExpansionTile(
title: _buildTitle(context),
leading: Text(result.rssi.toString()),
trailing: RaisedButton(
child: Text('CONNECT'),
color: Colors.black,
textColor: Colors.white,
onPressed: (result.advertisementData.connectable) ? onTap : null,
),
children: <Widget>[
_buildAdvRow(
context, 'Complete Local Name', result.advertisementData.localName),
_buildAdvRow(context, 'Tx Power Level',
'${result.advertisementData.txPowerLevel ?? 'N/A'}'),
_buildAdvRow(context, 'Manufacturer Data',
getNiceManufacturerData(result.advertisementData.manufacturerData)),
_buildAdvRow(
context,
'Service UUIDs',
(result.advertisementData.serviceUuids.isNotEmpty)
? result.advertisementData.serviceUuids.join(', ').toUpperCase()
: 'N/A'),
_buildAdvRow(context, 'Service Data',
getNiceServiceData(result.advertisementData.serviceData)),
],
);
}
}
class ServiceTile extends StatelessWidget {
final BluetoothService service;
final List<CharacteristicTile> characteristicTiles;
const ServiceTile(
{Key? key, required this.service, required this.characteristicTiles})
: super(key: key);
#override
Widget build(BuildContext context) {
if (characteristicTiles.length > 0) {
return ExpansionTile(
title: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text('Service'),
Text('0x${service.uuid.toString().toUpperCase().substring(4, 8)}',
style: Theme.of(context).textTheme.body1?.copyWith(
color: Theme.of(context).textTheme.caption?.color))
],
),
children: characteristicTiles,
);
} else {
return ListTile(
title: Text('Service'),
subtitle:
Text('0x${service.uuid.toString().toUpperCase().substring(4, 8)}'),
);
}
}
}
class CharacteristicTile extends StatelessWidget {
final BluetoothCharacteristic characteristic;
final List<DescriptorTile> descriptorTiles;
final VoidCallback? onReadPressed;
final VoidCallback? onWritePressed;
final VoidCallback? onNotificationPressed;
const CharacteristicTile(
{Key? key,
required this.characteristic,
required this.descriptorTiles,
this.onReadPressed,
this.onWritePressed,
this.onNotificationPressed})
: super(key: key);
#override
Widget build(BuildContext context) {
return StreamBuilder<List<int>>(
stream: characteristic.value,
initialData: characteristic.lastValue,
builder: (c, snapshot) {
final value = snapshot.data;
return ExpansionTile(
title: ListTile(
title: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text('Characteristic'),
Text(
'0x${characteristic.uuid.toString().toUpperCase().substring(4, 8)}',
style: Theme.of(context).textTheme.body1?.copyWith(
color: Theme.of(context).textTheme.caption?.color))
],
),
subtitle: Text(value.toString()),
contentPadding: EdgeInsets.all(0.0),
),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
IconButton(
icon: Icon(
Icons.file_download,
color: Theme.of(context).iconTheme.color?.withOpacity(0.5),
),
onPressed: onReadPressed,
),
IconButton(
icon: Icon(Icons.file_upload,
color: Theme.of(context).iconTheme.color?.withOpacity(0.5)),
onPressed: onWritePressed,
),
IconButton(
icon: Icon(
characteristic.isNotifying
? Icons.sync_disabled
: Icons.sync,
color: Theme.of(context).iconTheme.color?.withOpacity(0.5)),
onPressed: onNotificationPressed,
)
],
),
children: descriptorTiles,
);
},
);
}
}
class DescriptorTile extends StatelessWidget {
final BluetoothDescriptor descriptor;
final VoidCallback? onReadPressed;
final VoidCallback? onWritePressed;
const DescriptorTile(
{Key? key,
required this.descriptor,
this.onReadPressed,
this.onWritePressed})
: super(key: key);
#override
Widget build(BuildContext context) {
return ListTile(
title: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text('Descriptor'),
Text('0x${descriptor.uuid.toString().toUpperCase().substring(4, 8)}',
style: Theme.of(context)
.textTheme
.body1
?.copyWith(color: Theme.of(context).textTheme.caption?.color))
],
),
subtitle: StreamBuilder<List<int>>(
stream: descriptor.value,
initialData: descriptor.lastValue,
builder: (c, snapshot) => Text(snapshot.data.toString()),
),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
IconButton(
icon: Icon(
Icons.file_download,
color: Theme.of(context).iconTheme.color?.withOpacity(0.5),
),
onPressed: onReadPressed,
),
IconButton(
icon: Icon(
Icons.file_upload,
color: Theme.of(context).iconTheme.color?.withOpacity(0.5),
),
onPressed: onWritePressed,
)
],
),
);
}
}
class AdapterStateTile extends StatelessWidget {
const AdapterStateTile({Key? key, required this.state}) : super(key: key);
final BluetoothState state;
#override
Widget build(BuildContext context) {
return Container(
color: Colors.redAccent,
child: ListTile(
title: Text(
'Bluetooth adapter is ${state.toString().substring(15)}',
style: Theme.of(context).primaryTextTheme.subhead,
),
trailing: Icon(
Icons.error,
color: Theme.of(context).primaryTextTheme.subhead?.color,
),
),
);
}
}
please help me!!!

Why variables in a class not getting changed ? Flutter

I just started learning flutter I needed to pass data between pages so I found it easy to do with static variables but now I'm trying to make a setting page. I made a class named Settings like this :
class Settings {
static bool darkMode = false;
static bool addTable = true;
static saveSetting() {
GetStorage().write("darkMode", Settings.darkMode);
GetStorage().write("addTable", Settings.addTable);
}
static setSetting() {
GetStorage.init();
Settings.darkMode = (GetStorage().read("darkMode") ?? false);
Settings.addTable = (GetStorage().read("addTable") ?? true);
}
}
And a Switch in that page like this :
Switch(
value: Settings.addTable,
onChanged: (_) {
setState(() {
Settings.addTable = !Settings.addTable;
Settings.saveSetting();
});
}),
but after reloading the app the values are not saved in GetStorage, strings are saved perfectly except this one.
And the whole code is here :
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:get_storage/get_storage.dart';
import 'widgets/menu_button.dart';
void main() async {
await GetStorage.init();
DataManagament dataManagament = DataManagament();
dataManagament.startingApp();
runApp(const MyApp());
}
class DataManagament {
static String reserveString = "";
static List<String> reserveList = [];
static String tableString = "";
static List<String> tableList = [];
void saveReserveList() {
GetStorage().write("reserveList", reserveList.toString());
}
void saveTableList() {
GetStorage().write("tableList", tableList.toString());
}
void startingApp() {
Settings.setSetting;
//reserve
reserveString = (GetStorage().read("reserveList") ?? "");
reserveString == ""
? reserveList = []
: reserveList =
reserveString.substring(1, reserveString.length - 1).split(",");
//table
tableString = (GetStorage().read("tableList") ?? "");
tableString == ""
? tableList = []
: tableList =
tableString.substring(1, tableString.length - 1).split(",");
}
}
class Settings {
static bool darkMode = false;
static bool addTable = true;
static saveSetting() {
GetStorage().write("darkMode", Settings.darkMode);
GetStorage().write("addTable", Settings.addTable);
}
static setSetting() {
Settings.darkMode = (GetStorage().read("darkMode") ?? false);
Settings.addTable = (GetStorage().read("addTable") ?? true);
}
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return const MaterialApp(
home: IntroPage(),
debugShowCheckedModeBanner: false,
);
}
}
//
//
//************************************************************
//
//
//
//
//************************************************************
// Reserve Page
//
class ReservePage extends StatefulWidget {
const ReservePage({Key? key}) : super(key: key);
#override
State<ReservePage> createState() => _ReservePageState();
}
class _ReservePageState extends State<ReservePage> {
final _nameController = TextEditingController();
final _minuteController = TextEditingController();
final _hourController = TextEditingController();
#override
Widget build(BuildContext context) {
Size size = MediaQuery.of(context).size;
return Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: () {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
content: SingleChildScrollView(
child: Column(
children: [
TextField(
controller: _nameController,
textDirection: TextDirection.rtl,
decoration: const InputDecoration(
hintTextDirection: TextDirection.rtl,
border: OutlineInputBorder(),
labelText: 'نام',
),
),
Row(
children: [
Expanded(
flex: 5,
child: TextField(
controller: _hourController,
maxLength: 2,
keyboardType: TextInputType.number,
decoration: const InputDecoration(
labelText: 'hour',
),
),
),
const Expanded(
flex: 3,
child: Center(
child: Text(
":",
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
),
),
Expanded(
flex: 5,
child: TextField(
controller: _minuteController,
maxLength: 2,
keyboardType: TextInputType.number,
decoration: const InputDecoration(
labelText: 'minute',
),
),
),
],
),
const SizedBox(height: 20),
Center(
child: OutlinedButton(
onPressed: () {
if (_hourController.text != "" &&
_nameController.text != "") {
setState(() {
DataManagament.reserveList
.add(_nameController.text);
DataManagament.reserveList.add(
"${_hourController.text}:${_minuteController.text}");
_hourController.clear();
_nameController.clear();
_minuteController.clear();
DataManagament().saveReserveList();
});
}
},
child: const Text(
"save",
style: TextStyle(
color: Colors.black,
),
),
style: OutlinedButton.styleFrom(
minimumSize: Size(
size.width / 3,
(size.width / 3) * 0.4,
),
backgroundColor: Colors.green,
),
),
)
],
),
),
title: const Center(child: Text("")),
);
});
},
child: const FittedBox(
child: Icon(
Icons.add,
),
),
),
appBar: AppBar(
backgroundColor: Colors.white,
foregroundColor: Colors.black,
title: const Text(
"",
),
centerTitle: true,
),
body: DataManagament.reserveList.isNotEmpty
? SingleChildScrollView(
child: Column(
children: [
ListView.builder(
shrinkWrap: true,
itemCount: DataManagament.reserveList.length ~/ 2,
itemBuilder: (BuildContext context, int index) {
return Card(
// name 0 , time ,1
child: ListTile(
//name
trailing: Text(
DataManagament.reserveList[index * 2],
),
//time
title: Text(
DataManagament.reserveList[(index * 2) + 1],
),
leading: TextButton(
onPressed: () {
setState(() {
DataManagament.reserveList.removeAt(index * 2);
DataManagament.reserveList.removeAt(index * 2);
DataManagament().saveReserveList();
});
},
child: const Text("delete"),
),
));
},
),
],
),
)
: const Center(
child: Text("..."),
),
);
}
}
//
//
//************************************************************
// Menu Page
//
class MenuPage extends StatelessWidget {
const MenuPage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
actions: [
IconButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const SettingsPage(),
));
},
icon: const Icon(Icons.settings),
),
],
backgroundColor: Colors.white,
foregroundColor: Colors.black,
centerTitle: true,
title: const Text(
"",
),
),
body: Column(
children: [
const Spacer(flex: 1),
Expanded(
child: Center(
child: MenuButton(
func: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const TablePage(),
));
},
text: "tables"),
),
flex: 2),
Expanded(
child: Center(
child: MenuButton(
func: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const ReservePage(),
));
},
text: "رزروها"),
),
flex: 2),
Expanded(
child: Center(
child: MenuButton(func: () {}, text: "test"),
),
flex: 2),
const Spacer(
flex: 4,
)
],
),
);
}
}
//
//
//************************************************************
// Tables Page
//
class TablePage extends StatefulWidget {
const TablePage({Key? key}) : super(key: key);
#override
State<TablePage> createState() => _TablePageState();
}
class _TablePageState extends State<TablePage> {
final _nameController = TextEditingController(); // ignore: unused_field
final _minuteController = TextEditingController(); // ignore: unused_field
final _hourController = TextEditingController(); // ignore: unused_field
#override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: Settings.addTable
? GestureDetector(
onLongPress: () {
setState(() {
Settings.addTable = false;
});
},
child: FloatingActionButton(
onPressed: () {
setState(() {
});
},
child: const Icon(
Icons.add,
),
),
)
: null,
appBar: AppBar(
backgroundColor: Colors.white,
foregroundColor: Colors.black,
title: const Text(
"tables",
),
centerTitle: true,
),
body: DataManagament.tableList.isNotEmpty
? SingleChildScrollView(
child: Column(
children: [
ListView.builder(
shrinkWrap: true,
itemCount: DataManagament.tableList.length,
itemBuilder: (BuildContext context, int index) {
return Card(
// name 0 , time ,1
child: ListTile(
//name
trailing: Row(
children: [
Text(index.toString()),
TextButton(
onPressed: () {
setState(() {
DataManagament.tableList[index] =
_nameController.text;
});
},
child: const Text(""),
),
],
),
//time
title: TextButton(
onPressed: () {},
child: const Text(""),
),
leading: TextButton(
onPressed: () {},
child: Text(
DataManagament.tableList[index].toString()),
),
),
);
},
),
],
),
)
: const Center(
child: Text("..."),
),
);
}
}
//
//
//************************************************************
// Intro Page
//
class IntroPage extends StatefulWidget {
const IntroPage({Key? key}) : super(key: key);
#override
State<IntroPage> createState() => _IntroPageState();
}
class _IntroPageState extends State<IntroPage> {
Timer? _timer;
void startTimer() {
_timer = Timer(const Duration(seconds: 3), () {
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => const MenuPage(),
));
});
}
#override
void initState() {
super.initState();
startTimer();
}
#override
void dispose() {
_timer!.cancel();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
Expanded(
child: Center(
child: Image.asset("images/eightball.png"),
),
flex: 4),
const Expanded(
child: Center(
child: CircularProgressIndicator(),
),
),
],
),
);
}
}
//
//
//************************************************************
// Settings Page
//
class SettingsPage extends StatefulWidget {
const SettingsPage({Key? key}) : super(key: key);
#override
State<SettingsPage> createState() => _SettingsPageState();
}
class _SettingsPageState extends State<SettingsPage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.white,
foregroundColor: Colors.black,
title: const Text("settings"),
),
body: ListView(children: [
Card(
child: ListTile(
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: const [
Text(
"add table",
style: TextStyle(
fontWeight: FontWeight.bold,
),
),
SizedBox(width: 10),
Icon(Icons.add, size: 30),
],
),
leading: Switch(
value: Settings.addTable,
onChanged: (_) {
setState(() {
Settings.addTable = !Settings.addTable;
Settings.saveSetting();
});
}),
),
),
Card(
child: ListTile(
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: const [
Text(
"night mode",
style: TextStyle(
fontWeight: FontWeight.bold,
),
),
SizedBox(width: 20),
Icon(Icons.dark_mode),
],
),
leading: Switch(
value: Settings.darkMode,
onChanged: (_) {
setState(() {
Settings.darkMode = !Settings.darkMode;
Settings.saveSetting();
});
}),
),
),
]),
);
}
}
You need to call
final settings = new Settings();
settings.setSettings();
in a page where you want to access settings
I think you are just reading data from GetStorage inside setSettings instead of writing to storage, so when the app releods or restarts data inside static variables will not be available. as:
static setSetting() {
GetStorage.init();
Settings.darkMode = (GetStorage().read("darkMode") ?? false);
Settings.addTable = (GetStorage().read("addTable") ?? true);
//Here you are just updating your settings variable.
//Update you storage also to keep the selection in storage
}
Hope it works.

Why isn't Navigator.pop() refreshing data?

Hi guys I'm trying to build an app with flutter, so I have two screens HomeScreen() and RoutineScreen(). The first one is a Scaffold and in the body has a child Widget (a ListView called RoutinesWidget()) with all the routines. And the second one is to create a routine. The thing is, that when I create the routine, I use a button to pop to the HomeScreen() but it doesn't refresh the ListView (I'm guessing that it's because when I use Navigator.pop() it refreshes the Scaffold but not the child Widget maybe?)
HomeScreen() code here:
import 'package:flutter/material.dart';
import 'package:workout_time/constants.dart';
import 'package:workout_time/Widgets/routines_widget.dart';
import 'package:workout_time/Widgets/statistics_widget.dart';
import 'package:workout_time/Screens/settings_screen.dart';
import 'package:workout_time/Screens/routine_screen.dart';
class HomeScreen extends StatefulWidget {
#override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
int _selectedIndex = 0;
List<Widget> _views = [
RoutinesWidget(),
StatisticsWidget(),
];
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: kThirdColor,
appBar: AppBar(
leading: Icon(Icons.adb),
title: Text("Workout Time"),
actions: <Widget>[
IconButton(
icon: Icon(Icons.settings),
onPressed: () => Navigator.push(context,
MaterialPageRoute(builder: (context) => SettingsScreen()))),
],
),
body: _views[_selectedIndex],
floatingActionButton: (_selectedIndex == 1)
? null
: FloatingActionButton(
onPressed: () async {
await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => RoutineScreen(null)));
setState(() {});
},
child: Icon(
Icons.add,
color: kSecondColor,
size: 30.0,
),
elevation: 15.0,
),
bottomNavigationBar: BottomNavigationBar(
items: <BottomNavigationBarItem>[
bottomItems(Icon(Icons.fitness_center_rounded), "Routines"),
bottomItems(Icon(Icons.leaderboard_rounded), "Statistics"),
],
currentIndex: _selectedIndex,
onTap: (int index) => setState(() => _selectedIndex = index),
),
);
}
}
BottomNavigationBarItem bottomItems(Icon icon, String label) {
return BottomNavigationBarItem(
icon: icon,
label: label,
);
}
RoutinesWidget() code here:
import 'package:flutter/material.dart';
import 'package:workout_time/Services/db_crud_service.dart';
import 'package:workout_time/Screens/routine_screen.dart';
import 'package:workout_time/constants.dart';
import 'package:workout_time/Models/routine_model.dart';
class RoutinesWidget extends StatefulWidget {
#override
_RoutinesWidgetState createState() => _RoutinesWidgetState();
}
class _RoutinesWidgetState extends State<RoutinesWidget> {
DBCRUDService helper;
#override
void initState() {
super.initState();
helper = DBCRUDService();
}
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: helper.getRoutines(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasData) {
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (BuildContext context, int index) {
Routine routine = Routine.fromMap(snapshot.data[index]);
return Card(
margin: EdgeInsets.all(1.0),
child: ListTile(
leading: CircleAvatar(
child: Text(
routine.name[0],
style: TextStyle(
color: kThirdOppositeColor,
fontWeight: FontWeight.bold),
),
backgroundColor: kAccentColor,
),
title: Text(routine.name),
subtitle: Text(routine.exercises.join(",")),
trailing: IconButton(
icon: Icon(Icons.delete_rounded),
color: Colors.redAccent,
onPressed: () {
setState(() {
helper.deleteRoutine(routine.id);
});
},
),
onTap: () => Navigator.push(
context,
MaterialPageRoute(
builder: (context) => RoutineScreen(routine))),
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20.0)),
color: kSecondColor,
);
},
);
} else {
return Center(
child: CircularProgressIndicator(),
);
}
},
);
}
}
RoutineScreen() code here:
import 'package:flutter/material.dart';
import 'package:workout_time/Models/routine_model.dart';
import 'package:workout_time/Widgets/type_card_widget.dart';
import 'package:workout_time/constants.dart';
import 'package:workout_time/Services/db_crud_service.dart';
class RoutineScreen extends StatefulWidget {
final Routine _routine;
RoutineScreen(this._routine);
#override
_RoutineScreenState createState() => _RoutineScreenState();
}
class _RoutineScreenState extends State<RoutineScreen> {
DBCRUDService helper;
final _nameController = TextEditingController();
final _descriptionController = TextEditingController();
bool _type = true;
int _cycles = 1;
int _restBetweenExercises = 15;
int _restBetweenCycles = 60;
#override
void initState() {
super.initState();
helper = DBCRUDService();
}
#override
Widget build(BuildContext context) {
return DefaultTabController(
length: 2,
child: Scaffold(
appBar: AppBar(
leading: IconButton(
icon: Icon(Icons.arrow_back),
onPressed: () => Navigator.pop(context),
),
title: widget._routine != null
? Text(widget._routine.name)
: Text("Create your routine"),
actions: [
IconButton(
icon: Icon(Icons.done_rounded),
onPressed: createRoutine,
)
],
bottom: TabBar(
tabs: [
Tab(
text: "Configuration",
),
Tab(
text: "Exercises",
),
],
),
),
body: TabBarView(children: [
//_routine == null ? ConfigurationNewRoutine() : Text("WIDGET N° 1"),
ListView(
children: [
Container(
padding: EdgeInsets.all(15.0),
child: Row(
children: [
Text(
"Name:",
style: TextStyle(
fontSize: 20.0,
fontWeight: FontWeight.bold,
),
),
SizedBox(
width: 40.0,
),
Expanded(
child: TextField(
textAlign: TextAlign.center,
controller: _nameController,
),
),
],
),
),
SizedBox(
height: 20.0,
),
Card(
margin: EdgeInsets.all(15.0),
color: kSecondColor,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20.0),
),
child: Container(
padding: EdgeInsets.all(15.0),
child: Column(
children: [
Text(
"Type",
style: TextStyle(fontSize: 25.0),
),
Row(
children: [
Expanded(
child: TypeCard(
Icons.double_arrow_rounded,
_type == true ? kFirstColor : kThirdColor,
() => setState(() => _type = true),
"Straight set",
),
),
Expanded(
child: TypeCard(
Icons.replay_rounded,
_type == false ? kFirstColor : kThirdColor,
() => setState(() => _type = false),
"Cycle",
),
),
],
),
],
),
),
),
SizedBox(
height: 20.0,
),
Container(
padding: EdgeInsets.all(15.0),
child: Row(
children: [
Text(
"N° cycles:",
style: TextStyle(
fontSize: 20.0,
fontWeight: FontWeight.bold,
),
),
SizedBox(
width: 40.0,
),
Expanded(
child: Text("Hello"),
),
],
),
),
SizedBox(
height: 20.0,
),
],
),
Text("WIDGET N° 2"),
]),
),
);
}
void createRoutine() {
List<String> _exercises = ["1", "2"];
List<String> _types = ["t", "r"];
List<String> _quantities = ["30", "20"];
Routine routine = Routine({
'name': _nameController.text,
'description': "_description",
'type': _type.toString(),
'cycles': 1,
'numberExercises': 2,
'restBetweenExercises': 15,
'restBetweenCycles': 60,
'exercises': _exercises,
'types': _types,
'quantities': _quantities,
});
setState(() {
helper.createRoutine(routine);
Navigator.pop(context);
});
}
}
Any idea what can I do to make it work? Thank you
Make it simple
use Navigator.pop() twice
so that the current class and old class in also removed
from the stack
and then use Navigator.push()
When you push a new Route, the old one still stays in the stack. The new route just overlaps the old one and forms like a layer above the old one. Then when you pop the new route, it will just remove the layer(new route) and the old route will be displayed as it was before.
Now you must be aware the Navigator.push() is an asynchronous method and returns a Future. How it works is basically when you perform a Navigator.push(), it will push the new route and will wait for it to be popped out. Then when the new route is popped, it returns a value to the old one and that when the future callback will be executed.
Hence the solution you are looking for is add a future callback like this after your Navigator.push() :
Navigator.push(context,
MaterialPageRoute(builder: (context) => SettingsScreen())
).then((value){setState(() {});}); /// A callback which is executed after the new route will be popped. In that callback, you simply call a setState and refresh the page.

Flutter how to save position of the reorderables package?

i tried the reorderables package https://pub.dev/packages/reorderables
I successed to move my dashboard blocs but when I restart app, my moves are removed.
So the solution can only be a sharedpref solution.
But I dont found how to save this information
I tried to save and load newIndex but without success
I tried to save and load List _tiles; but sharedpref can't save List
Here is my code example
List<Widget> _tiles;
void _onReorder(int oldIndex, int newIndex) async {
SharedPreferences prefs = await SharedPreferences.getInstance();
setState(() {
Widget row = _tiles.removeAt(oldIndex);
_tiles.insert(newIndex, row);
//prefs.setListWidget('indexList', _tiles); not working
// prefs.setInt('index', newIndex ); not working
});
}
#override
void initState() {
super.initState();
_tiles = <Widget>[
//my widget1
//my widget2
//my widget3
//my widget4
//my widget5
//my widget6
//my widget7
//my widget8
//my widget9
]
}
#override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
ReorderableWrap(
spacing: 0.0,
runSpacing:0,
maxMainAxisCount: 3,
minMainAxisCount: 3,
padding: const EdgeInsets.all(5),
children:_tiles,
onReorder: _onReorder,
onNoReorder: (int index) {
//this callback is optional
debugPrint('${DateTime.now().toString().substring(5, 22)} reorder cancelled. index:$index');
},
onReorderStarted: (int index) {
//this callback is optional
debugPrint('${DateTime.now().toString().substring(5, 22)} reorder started: index:$index');
}
)
]
);
}
}
edit : here is Widget 1. other widget are same
new Container (
width: SizeConfig.safeBlockHorizontal * 32,
height: 160,
child :
new Card(
elevation: 8,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
color: Colors.white,
child:
Ink(
child: InkResponse(
splashFactory: InkRipple.splashFactory,
radius: 100,
onTap: () {
},
child:
Padding(
padding: const EdgeInsets.only(top:10),
child:
new Container(
padding: const EdgeInsets.all(0),
child :
new Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
new Container(
height:25,
child :
new Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Icon(Icons.keyboard_arrow_right, color: Colors.white, size: 15.0),
Text('Planning',textAlign:TextAlign.center, style: TextStyle(
color: Colors.black,
fontWeight: FontWeight.w500,
fontSize: SizeConfig.safeBlockHorizontal * 4),
),
Icon(Icons.keyboard_arrow_right, color: Colors.grey, size: 15.0),
]
),
),
Padding(
padding: const EdgeInsets.all(5),),
Icon(Icons.event_available, color:Color(0xffff9a7b), size: 70.0),
],
),
))
),
)),
)
Here is my design
Edit #2
Now I tried to add my assets in the model, but I don't know how to do
void initState() {
// default models list
models = [
Model(index: 0, new Container (
width:90,
height: 90,
child :new FlareActor("assets/pierre.flr", alignment:Alignment.center, fit:BoxFit.contain, animation:"pierre")
), title: 'Coach'),
Model(index: 1, new Image.asset(
"assets/cup.png",
width: 50,
), title: 'Victories'),
Model(index: 2, icon: Icon(Icons.card_giftcard), title: 'Jeux'),
Model(index: 3, icon: Icon(Icons.wb_sunny), title: 'Sunny'),
Model(index: 4, icon: Icon(Icons.cloud), title: 'Cloud'),
Model(index: 5, icon: Icon(Icons.tv), title: 'TV'),
Model(index: 6, icon: Icon(Icons.place), title: 'Location'),
Model(index: 8, icon: Icon(Icons.music_note), title: 'Music'),
// More customization
Model(
index: 7,
icon: Icon(Icons.event_available, color: Color(0xffff9a7b)),
title: 'Planning'),
];
config();
super.initState();
}
This solution isn't a good one and I don't like it, but it works :D
I really appreciate it if anyone would refer a better one
Maybe for this project, it's good to use DB instead of SharedPreferences but here I used SharedPreferences.
The question is how to save the order of some widget(each time on reordering, the order of widget changes and we want to save the order of them, after restarting the app the saved order should be fetched).
SharedPreferences can also save a list of string, so what I did here was:
In the beginning, there should be a default list, that contains the initial order of widget's of the app.
Because widgets are somehow the same and only some of their info is different, I decided to define a model and work with models, instead of a whole complicated widget, I mean when I want to remove or change indexes I do it for a list of models rather than a list of widgets.
Here I supposed the model only contains a title, I also defined an index for it, so all I do is that when I reorder the widget, it reorders the list of models, to save the order, I save the index of models in any order they are now,
for example, if the initial order was [0, 1, 2, 3] let's say after reordering it's now [3, 0, 1, 2], I save this order, and for the next boot, I fetch the saved order([3, 0, 1, 2]) and then reorder the default list based on this fetched order.
Another solution would be to change the model's index and then show an ordered list of models based on their index.
Here is the code:
import 'package:flutter/material.dart';
import 'package:reorderables/reorderables.dart';
import 'package:shared_preferences/shared_preferences.dart';
void main() => runApp(
MaterialApp(
home: Scaffold(
body: Center(
child: Page(),
),
),
),
);
class Page extends StatefulWidget {
const Page({Key key}) : super(key: key);
#override
_PageState createState() => _PageState();
}
class _PageState extends State<Page> {
SharedPreferences prefs;
List<Model> models;
#override
void initState() {
// default models list
models = [
Model(index: 0, title: 'Item 0'),
Model(index: 1, title: 'Item 1'),
Model(index: 2, title: 'Item 2'),
Model(index: 3, title: 'Item 3'),
];
config();
super.initState();
}
void config() async {
// Here we reset the default model based on saved order
await SharedPreferences.getInstance().then((pref) {
prefs = pref;
List<String> lst = pref.getStringList('indexList');
List<Model> list = [];
if (lst != null && lst.isNotEmpty) {
list = lst
.map(
(String indx) => models
.where((Model item) => int.parse(indx) == item.index)
.first,
)
.toList();
models = list;
}
setState(() {});
});
}
void _onReorder(int oldIndex, int newIndex) async {
Model row = models.removeAt(oldIndex);
models.insert(newIndex, row);
setState(() {
prefs.setStringList(
'indexList', models.map((m) => m.index.toString()).toList());
});
}
#override
Widget build(BuildContext context) {
return ReorderableWrap(
scrollDirection: Axis.vertical,
direction: Axis.vertical,
spacing: 0.0,
runSpacing: 0,
maxMainAxisCount: 3,
minMainAxisCount: 3,
padding: const EdgeInsets.all(5),
children: models
.map((m) => Card(
child: Container(
child: Text('${m.index} - ${m.title}'),
padding: EdgeInsets.all(24.0),
),
))
.toList(),
onReorder: _onReorder,
onNoReorder: (int index) {
//this callback is optional
debugPrint('${DateTime.now().toString().substring(5, 22)} ' +
'reorder cancelled. index:$index');
},
onReorderStarted: (int index) {
//this callback is optional
debugPrint('${DateTime.now().toString().substring(5, 22)} ' +
'reorder started: index:$index');
});
}
}
class Model {
int index;
String title;
Model({this.index, this.title});
#override
String toString() {
return '$index : $title';
}
}
Changes After Edit to the main question:
This version is based on the editing to the main question, I decided to keep the first answer unchanged because it's a more simple version and may help another viewer.
For the new model, as far as I could get, it has an icon, title, and an onTap functionality, I changed the model to have icon and title, but for the onTap, I wrote my own card version that gets a model and onTap functionality, I could add onTap to the model, but I thought it's better for future use or to use in other places, so I separated the onTap from the model, I also chose Icon for the model, it could be IconData (benefit of IconData is that you can choose customization for each icon and etc).
On my Card version (MyCard), I simply used a GestureDetector and Card to simulate the taps and card.
I wrote a FakePage that gets a model and if you Tap on each card it navigates to this page and shows some message based on the received model.
To clean the previously saved model in SharedPreferences, you should comment the part that fetches models order in config() and on the next refresh, you should uncomment it again.
Here is the new version of code:
import 'package:flutter/material.dart';
import 'package:reorderables/reorderables.dart';
import 'package:shared_preferences/shared_preferences.dart';
void main() => runApp(
MaterialApp(
home: Scaffold(
body: Center(
child: Page(),
),
),
),
);
class Page extends StatefulWidget {
const Page({Key key}) : super(key: key);
#override
_PageState createState() => _PageState();
}
class _PageState extends State<Page> {
SharedPreferences prefs;
List<Model> models;
#override
void initState() {
// default models list
models = [
Model(index: 0, icon: Icon(Icons.people), title: 'Coach'),
Model(index: 1, icon: Icon(Icons.wb_incandescent), title: 'Victories'),
Model(index: 2, icon: Icon(Icons.card_giftcard), title: 'Jeux'),
Model(index: 3, icon: Icon(Icons.wb_sunny), title: 'Sunny'),
Model(index: 4, icon: Icon(Icons.cloud), title: 'Cloud'),
Model(index: 5, icon: Icon(Icons.tv), title: 'TV'),
Model(index: 6, icon: Icon(Icons.place), title: 'Location'),
Model(index: 8, icon: Icon(Icons.music_note), title: 'Music'),
// More customization
Model(
index: 7,
icon: Icon(Icons.event_available, color: Color(0xffff9a7b)),
title: 'Planning'),
];
config();
super.initState();
}
void config() async {
// Here we reset the default model based on saved order
await SharedPreferences.getInstance().then((pref) {
prefs = pref;
List<String> lst = pref.getStringList('indexList');
List<Model> list = [];
if (lst != null && lst.isNotEmpty) {
list = lst
.map(
(String indx) => models
.where((Model item) => int.parse(indx) == item.index)
.first,
)
.toList();
models = list;
}
setState(() {});
});
}
void _onReorder(int oldIndex, int newIndex) async {
Model row = models.removeAt(oldIndex);
models.insert(newIndex, row);
setState(() {
prefs.setStringList(
'indexList', models.map((m) => m.index.toString()).toList());
});
}
#override
Widget build(BuildContext context) {
return ReorderableWrap(
spacing: 0.0,
runSpacing: 0,
maxMainAxisCount: 3,
minMainAxisCount: 3,
padding: const EdgeInsets.all(5),
children: <Widget>[
MyCard(
model: models[0],
onTap: () => Navigator.of(context).push(
MaterialPageRoute(
builder: (BuildContext context) => FakePage(model: models[0]),
),
),
),
MyCard(
model: models[1],
onTap: () => Navigator.of(context).push(
MaterialPageRoute(
builder: (BuildContext context) => FakePage(model: models[1]),
),
),
),
MyCard(
model: models[2],
onTap: () => Navigator.of(context).push(
MaterialPageRoute(
builder: (BuildContext context) => FakePage(model: models[2]),
),
),
),
MyCard(
model: models[3],
onTap: () => Navigator.of(context).push(
MaterialPageRoute(
builder: (BuildContext context) => FakePage(model: models[3]),
),
),
),
MyCard(
model: models[4],
onTap: () => Navigator.of(context).push(
MaterialPageRoute(
builder: (BuildContext context) => FakePage(model: models[4]),
),
),
),
MyCard(
model: models[5],
onTap: () => Navigator.of(context).push(
MaterialPageRoute(
builder: (BuildContext context) => FakePage(model: models[5]),
),
),
),
MyCard(
model: models[6],
onTap: () => Navigator.of(context).push(
MaterialPageRoute(
builder: (BuildContext context) => FakePage(model: models[6]),
),
),
),
MyCard(
model: models[7],
onTap: () => Navigator.of(context).push(
MaterialPageRoute(
builder: (BuildContext context) => FakePage(model: models[7]),
),
),
),
MyCard(
model: models[8],
onTap: () => Navigator.of(context).push(
MaterialPageRoute(
builder: (BuildContext context) => FakePage(model: models[8]),
),
),
),
],
onReorder: _onReorder,
onNoReorder: (int index) {
//this callback is optional
debugPrint('${DateTime.now().toString().substring(5, 22)} ' +
'reorder cancelled. index:$index');
},
onReorderStarted: (int index) {
//this callback is optional
debugPrint('${DateTime.now().toString().substring(5, 22)} ' +
'reorder started: index:$index');
});
}
}
// ---------------------- Model --------------------------
class Model {
int index;
String title;
Icon icon;
Model({this.index, this.title, this.icon});
#override
String toString() {
return '$index : $title';
}
}
// ------------------------ MyCard ----------------------------
class MyCard extends StatelessWidget {
final Model model;
final void Function() onTap;
const MyCard({Key key, this.onTap, #required this.model})
: assert(model != null),
super(key: key);
#override
Widget build(BuildContext context) {
double width = MediaQuery.of(context).size.width;
return GestureDetector(
onTap: onTap,
child: Card(
elevation: 8.0,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
child: _child(width),
),
);
}
Widget _child(double width) {
return Container(
width: width / 4,
height: width / 3,
margin: EdgeInsets.all(5.0),
child: Column(
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Expanded(
flex: 3,
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Text(
model.title,
maxLines: 1,
textAlign: TextAlign.center,
style: TextStyle(
color: Colors.black,
fontWeight: FontWeight.w500,
fontSize: 15.0,
),
overflow: TextOverflow.ellipsis,
),
Icon(
Icons.arrow_forward_ios,
color: Colors.grey.shade400,
size: 15.0,
),
],
),
),
Expanded(
flex: 5,
child: Padding(
padding: EdgeInsets.all(8.0),
child: FittedBox(
fit: BoxFit.contain,
child: model.icon,
),
),
),
],
),
);
}
}
// ----------------------- FAKE PAGE ---------------------------
class FakePage extends StatelessWidget {
final Model model;
const FakePage({Key key, this.model}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.deepOrangeAccent,
),
body: Center(
child: Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Text(
'You Clicked on Card : ${model.title}',
style: TextStyle(fontSize: 20.0),
),
Padding(
padding: EdgeInsets.only(top: 24.0),
child: Icon(
model.icon.icon,
size: 70.0,
),
),
],
),
),
);
}
}