I have a scenario, where I want to change state of loading class while I load my data on screen. So for that I am trying to switch the initial state of a provider from another provider but throws me an error. "Providers are not allowed to modify other providers during their initialisation." I need to know the very best practice to handle this kind of scenarios. My
classes are as follow:
class CleansingServices extends StateNotifier<List<CleansingBaseModel>> {
CleansingServices() : super([]);
void setServices(List<CleansingBaseModel> data) {
state = data;
}
}
final cleansingServicesProvider = StateNotifierProvider<CleansingServices, List<CleansingBaseModel>>((ref) {
final data = ref.watch(loadServicesProvider);
final dataLoading = ref.watch(cleansingLoadingStateProvider.notifier);
data.when(
data: (data) {
ref.notifier.setServices(data);
dataLoading.setNotLoading();
},
error: (error, str) {
dataLoading.setNotLoadingWithError(error);
},
loading: () {
dataLoading.setLoading();
},
);
return CleansingServices();
});
class CleansingServices extends StateNotifier<List<CleansingBaseModel>> {
var data ;
CleansingServices(this.data) : super([]){
data.when(
data: (data) {
ref.notifier.setServices(data);
dataLoading.setNotLoading();
},
error: (error, str) {
dataLoading.setNotLoadingWithError(error);
},
loading: () {
dataLoading.setLoading();
},
);
}
void setServices(List<CleansingBaseModel> data) {
state = data;
}
}
final cleansingServicesProvider = StateNotifierProvider<CleansingServices, List<CleansingBaseModel>>((ref) {
final data = ref.watch(loadServicesProvider);
final dataLoading = ref.watch(cleansingLoadingStateProvider.notifier);
return CleansingServices(data );
});
Related
class QuestionPaperController extends StateNotifier<List<String>> {
QuestionPaperController() : super([]);
Future<void> getAllPapers(WidgetRef ref) async {
List<String> imgName = ["biology", "chemistry", "maths", "physics"];
try {
for (var img in imgName) {
final imgUrl = await ref.read(firebaseStorageProvider).getImage(img);
state = [...state, imgUrl!];
}
} catch (e) {
print(e);
}
}
}
final questionPaperControllerProvider =
StateNotifierProvider<QuestionPaperController, List<String>>((ref) {
return QuestionPaperController();
});
I want to add another list that its name will stackoverflow for this class and watch it but statenotifier listening another list what can I do?
You need to create another instance of the class
class StackoverflowController extends StateNotifier<List<String>> {
/// ...
}
final stackoverflowControllerProvider =
StateNotifierProvider<StackoverflowController, List<String>>((ref) {
return StackoverflowController();
});
and create provider that watch the other two
final otherProvider = Provider<...>((ref) {
ref.watch(stackoverflowControllerProvider);
ref.watch(questionPaperControllerProvider );
return ...;
});
bonus: you can pass ref in class-controller:
final fizzControllerPr = Provider.autoDispose((ref) => FizzController(ref));
// or use tear-off
final fizzControllerPr1 = Provider.autoDispose(FizzController.new);
/// Class represent controller.
class FizzController {
FizzController(this._ref);
final Ref _ref;
Future<void> getAllPapers() async {
//...
final imgUrl = await _ref.read(firebaseStorageProvider).getImage(img);
//...
}
}
I am trying to use a connected bluetooth device on other pages, but I'm unable to do that. I tried to use the provider, but that did not work, parameter passing did not work either.
After testing, I am using the following
I made a class ReactiveProvider
class ReactiveProvider(){
Stream<ConnectionStateUpdate> get currentConnectionStream {
return flutterReactiveBle.connectToAdvertisingDevice(
id: _foundBleUARTDevices[index].id,
prescanDuration: const Duration(seconds: 1),
withServices: [_uartUuid, _uartRx, _uartTx],
);
}
}
and setup in start
void main() {
runApp(
MultiProvider(providers: [
StreamProvider<ConnectionStateUpdate>(
create: (context) => ReactiveProvider().currentConnectionStream,
initialData: const ConnectionStateUpdate(
deviceId: "",
connectionState: DeviceConnectionState.disconnected,
failure: null),
)
], child: const MainApp()),
);
}
and in StatefullWidget
final _currentConnectionStream = Provider.of<ConnectionStateUpdate>(context);
I got the errors
The instance member 'context' can't be accessed in an initializer.
Try replacing the reference to the instance member with a different expression
and
The method 'listen' isn't defined for the type 'ConnectionStateUpdate'.
Try correcting the name to the name of an existing method, or defining a method named 'listen'.
In following function
_connection = _currentConnectionStream.listen((event) {});
I want to access the following parameters on another page using any state management
final flutterReactiveBle = FlutterReactiveBle();
List<DiscoveredDevice> _foundBleUARTDevices = [];
late StreamSubscription<DiscoveredDevice> _scanStream;
late Stream<ConnectionStateUpdate> _currentConnectionStream;
late StreamSubscription<ConnectionStateUpdate> _connection;
late QualifiedCharacteristic _txCharacteristic;
//late QualifiedCharacteristic _rxCharacteristic;
late Stream<List<int>> _receivedDataStream;
These are other functions I am using
void onNewReceivedData(List<int> data) {
_numberOfMessagesReceived += 1;
_receivedData
.add("$_numberOfMessagesReceived: ${String.fromCharCodes(data)}");
if (_receivedData.length > 10) {
_receivedData.removeAt(0);
}
}
void _disconnect() async {
await _connection.cancel();
_connected = false;
}
void _stopScan() async {
await _scanStream.cancel();
_scanning = false;
}
void _startScan() async {
_foundBleUARTDevices = [];
_scanning = true;
_scanStream = flutterReactiveBle
.scanForDevices(withServices: [_uartUuid]).listen((device) {
if (_foundBleUARTDevices.every((element) => element.id != device.id)) {
_foundBleUARTDevices.add(device);
}
}, onError: (Object error) {
_logTexts = "${_logTexts}ERROR while scanning:$error \n";
}, onDone: () async {
await _scanStream.cancel();
_scanning = false;
});
}
void onConnectDevice(index) {
_currentConnectionStream = flutterReactiveBle.connectToAdvertisingDevice(
id: _foundBleUARTDevices[index].id,
prescanDuration: const Duration(seconds: 1),
withServices: [_uartUuid, _uartRx, _uartTx],
);
_logTexts = "";
_connection = _currentConnectionStream.listen((event) {
var id = event.deviceId.toString();
switch (event.connectionState) {
case DeviceConnectionState.connecting:
{
_logTexts = "${_logTexts}Connecting to $id\n";
break;
}
case DeviceConnectionState.connected:
{
_connected = true;
_logTexts = "${_logTexts}Connected to $id\n";
_numberOfMessagesReceived = 0;
_receivedData = [];
_txCharacteristic = QualifiedCharacteristic(
serviceId: _uartUuid,
characteristicId: _uartTx,
deviceId: event.deviceId);
_receivedDataStream =
flutterReactiveBle.subscribeToCharacteristic(_txCharacteristic);
_receivedDataStream.listen((data) {
onNewReceivedData(data);
}, onError: (dynamic error) {
_logTexts = "${_logTexts}Error:$error$id\n";
});
break;
}
case DeviceConnectionState.disconnecting:
{
_connected = false;
_logTexts = "${_logTexts}Disconnecting from $id\n";
break;
}
case DeviceConnectionState.disconnected:
{
_logTexts = "${_logTexts}Disconnected from $id\n";
break;
}
}
});
}
Another question I have, is how I can use or keep connected using on void onConnectDevice(index) function, because as per the provider you don't need to pass the parameters.
My app starts with retrieving data that is important throughout the flow of the app mainContent. Most of this data is static
Navigation screens
Widget stackPages(WidgetRef ref) {
AsyncValue<Map<String, Object>> mainContent = ref.watch(mainContentFutureProvider);
return mainContent.when(
loading: () => Center(child: CircularProgressIndicator()),
error: (e, st) => Center(child: Text("Error: " + e.toString() + " " + st.toString())),
data: (content) {
return Stack(
children: [
_buildOffstageNavigator(ref, "Home", content),
_buildOffstageNavigator(ref, "Page1", content),
_buildOffstageNavigator(ref, "Page2", content),
_buildOffstageNavigator(ref, "Page3", content)
],
);
},
);
}
content retrieval (mainContentFutureProvider)
final mainContentFutureProvider= FutureProvider<Map<String, Object>>((ref) async {
List response = await Future.wait([
DataController.userInfoDB.getUsers(),
DataController.userInfoDB.getAnotherList(),
DataController.userInfoDB.getAnotherList,
]);
return {
"users": response[0],
"some_list": response[1],
"some_list": response[2],
};
},
);
User class (simplified)
class User{
String id;
String email;
List<Vehicle> vehicles = [];
User(this.email, this.vehicles);
User.fromJson(Map<String, dynamic> json)
: id = json['id'],
displayName = json['display_name'],
}
problem
in the garage screen of the app the user can add or remove vehicles. When a user adds or removes a vehicle this affects the entire flow of the app. So this User needs to have its Notifier class
CurrentUserNotifier
class CurrentUserNotifier extends StateNotifier<User> {
final User user;
CurrentUserNotifier(this.user) : super(null);
void addUserVehicle(Vehicle vehicle) {
state..vehicles.add(vehicle);
}
void removeUserVehicle(int vehicleId) {
state..vehicles.removeWhere((v) => v.id == vehicleId);
}
}
currentUserProvider
final currentUserProvider = StateNotifierProvider.family<CurrentUserNotifier, User, User>((ref, user) {
return CurrentUserNotifier(user);
});
Currently I am retrieving a List<User> and want only to have the current user to be coming from a provider in my app. As you see I have made a .family from StateNotifierProvider so I can perform the following thing:
content retrieval (mainContentFutureProvider)
final mainContentFutureProvider= FutureProvider<Map<String, Object>>((ref) async {
List response = await Future.wait([
DataController.userInfoDB.getUsers(),
DataController.userInfoDB.getAnotherList(),
DataController.userInfoDB.getAnotherList,
]);
---> currentUserProvider(response[0].first);
return {
"users": response[0],
"some_list": response[1],
"some_list": response[2],
};
},
);
But for any page that deals with my User object it needs to pass through the user object as parameter to my currentUserProvider
like:
press: () async {
ref.read(currentUserProvider(user).notifier).addUserVehicle(vehicle);
}
I want the provider just set the value of the StateNotifierProvider once, am I making a pattern/flow mistake here?
Try this:
Have your CurrentUserNotifier like so.
final currentUserProvider = StateNotifierProvider<CurrentUserNotifier, User>((ref) {
return CurrentUserNotifier();
});
class CurrentUserNotifier extends StateNotifier<User?> {
CurrentUserNotifier() : super(null);
void setUser(User user){
state = user;
}
void addUserVehicle(Vehicle vehicle) {
state = state..vehicles.add(vehicle);
}
void removeUserVehicle(int vehicleId) {
state = state..vehicles.removeWhere((v) => v.id == vehicleId);
}
}
Then set the user like so:
final mainContentFutureProvider= FutureProvider<Map<String, Object>>((ref) async {
List response = await Future.wait([
DataController.userInfoDB.getUsers(),
DataController.userInfoDB.getAnotherList(),
DataController.userInfoDB.getAnotherList,
]);
ref.read(currentUserProvider.notifier).setUser(response[0].first);
return {
"users": response[0],
"some_list": response[1],
"some_list": response[2],
};
},
);
Then you can do:
press: () async {
ref.read(currentUserProvider.notifier).addUserVehicle(vehicle);
}
I have a program that will check a password with bcrypt library, this is quite computing intensive, so as a result the UI will be stuck for like 2 seconds. It is very annoying and I cannot figure out what to do to stop it.
I want a loader to be shown when the password is being checked.
This is my code:
class _MyWidgetState<MyWidget> extends State{
build() {
return GetPassCode(PassCodeType.ENTER,
onDone: ({context, data}) async {
unlock(state, data?['password'] ?? '', Languages.of(context));
}, goBack: () {}, data: {});
}
unlock(userState, String? password, Languages strings) async {
final user = userState.currentUser;
if (!(await user.checkPassword(password))) {
return;
}
}
context.read<LockCubit>().unlock();
}
}
you can put the caculating into a isolate.
https://api.flutter-io.cn/flutter/dart-isolate/dart-isolate-library.html
here's some example code:
class IsoMessage {
final SendPort? sendPort;
final List<String> args;
IsoMessage(this.sendPort, this.args);
}
String myCaculate(IsoMessage message) {
String result = message.args[0][0] + message.args[1][1];
message.sendPort?.send(result);
return result;
}
here's how to calling the func
var port = ReceivePort();
port.listen((message) {
print("onData: $message");
}, onDone: () {
print('iso close');
}, onError: (error) {
print('iso error: $error');
});
IsoMessage message = IsoMessage(port.sendPort,["asd", "dsa"]);
Isolate.spawn<IsoMessage>(myCaculate, message);
I am new to React hooks/Context API. I have read the React hook/context docs, and I am still having trouble with the following:
My attempts to update global state by multiple consumer components
currently causes frequent overwriting of context state due to
rerendering (e.g., activity or details state is sometimes
null/undefined). This probably is why...
... I am getting 400 (bad request) and/or 500 (server) errors on random refreshes of the page (~30% of the time content loads as
expected, ~70% errors are thrown. I believe this is happening
because we have various context states that are being called
asynchronously).
I am not sure how to implement Axios Cancellation, given that our useEffect hooks are calling dispatch functions (e.g.,
getActivities()) in different files. The examples I've seen
involve fetching data within the component (rather than in context).
I am seeking assistance for #1 specifically. I would love guidance on how to accurately fetch data and store in context as global state, and then provide that context to child components, allowing them to consume/update context state without unnecessary rerendering.
Tried to only provide relevant code snippets below:
ActivityState.js -- should fetch activity data
...
const ActivityState = props => {
const initialState = {
activities: [],
isLoading: false,
isError: false
};
const HEADERS = {
'Content-Type': 'application/json',
'user_id': 1
}
const [state, dispatch] = useReducer(ActivityReducer, initialState);
const userContext = useContext(UserContext);
const getActivities = async () => {
const { loggedIn } = contactContext;
let didCancel = false; // attempts to start implementing axios cancellation
try {
const res = await axios.get(url);
dispatch({ type: GET_ACTIVITIES, payload: res.data.data.activities });
} catch (err) {
if (!didCancel) {
dispatch({ type: 'FETCH_FAILURE' });
}
}
}
const updateActivity = (path, data) => { //update context state
dispatch({ type: UPDATE_ACTIVITY, payload: { path: path, data: data } });
};
const saveActivity = () => { //send new activity data to the backend
const postData = {
actions: [{"293939": []}],
activities: state.activities
};
try {
const res = axios.post(url,{ data: postData }, { headers: HEADERS });
} catch (err) {
console.log(err);
}
}
return (
<ActivityContext.Provider
value={{
activities: state.activities,
data: state.data,
backup_data: state.backup_data,
getActivities,
updateActivity,
saveActivity,
}}
>
{props.children}
</ActivityContext.Provider>
);
};
export default ActivityState;
ActivityReducer.js -- switch statements to be dispatched by ActivityState.js
...
export default (state, action) => {
switch (action.type) {
case GET_ACTIVITIES:
return {
...state,
activities: action.payload,
isLoading: true
};
case FETCH_FAILURE:
return {
...state,
isLoading: false,
isError: true
};
case UPDATE_ACTIVITY:
const { payload: { path }, payload } = action;
const data = state;
if (!data.activities)
return { data };
const index = data.activities.findIndex(e => e.socium_tracking_number == path.id);
if(index === -1)
return { data };
_.set(data, `activities[${index}].${path.field}`, payload.data);
return {
data,
};
...
DetailsState.js -- dispatch functions to fetch details
const DetailsState = props => {
const initialState = {
details: null,
};
const [state, dispatch] = useReducer(DetailsReducer, initialState);
const getDetails = async () => {
try {
const res = await axios.get(url);
dispatch({ type: GET_DETAILS, payload: res.data.data[0].details});
}catch(err) {
console.log(err)
}
};
return (
<DetailsContext.Provider
value={{ details: state.details, getDetails }}
>
{ props.children }
</DetailsContext.Provider>
);
}
export default SchemaState;
DetailsReducer.js -- switch statement
export default (state, action) => {
switch (action.type) {
case GET_DETAILS:
return {
...state,
details: action.payload,
};
default:
return state;
}
};
ActivityTable.js -- component that consumes Activity Info
...
const ActivityTable = ({ activity }) => {
const activityContext = useContext(ActivityContext);
const { activities, filtered, getActivities } = activityContext;
const [order, setOrder] = React.useState('asc');
const [orderBy, setOrderBy] = React.useState(activities.wait_time);
// Get activity data on mount
useEffect(() => {
async function fetchData() {
await getActivities()
}
fetchData();
}, []);
...
CreateActivity.js -- component that consumes Activity and Details data
...
const CreateActivity = props => {
const activityContext = useContext(ActivityContext);
const { activities, filtered, getActivities, addActivity } = activityContext;
const detailsContext = useContext(DetailsContext);
const { details, getDetails } = detailsContext;
// Get activity and details data on mount
useEffect(() => {
async function fetchData() {
await getActivities();
await getSchema();
}
fetchData();
}, []);
...
I really tried to get smarter on these issues before approaching the SO community, so that my question(s) was more defined. But this is what I have. Happy to provide any info that I missed or clarify confusion. Thank you for your time