keep an integer with SharedPreferences in Flutter - flutter

I want to make a one time page only first time this page will open and ı don't know how to keep this command. I think I should use SharedPreferences but couldn't do that. Please help me.
import 'package:eventer/landing/second.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class LandingPage extends StatelessWidget {
int index = 0;
#override
Widget build(BuildContext context) {
return Center(
child: RaisedButton(
onPressed: selecter(),
),
);
}
selecter() {
if (index == 0) {
return FirstPage();
} else {
() {
return SecondPage();
};
}
}
}

you can install sharedpreference dependency using pubspec.yaml file
shared_preferences: ^0.5.4
then in your .dart file import package
import 'package:shared_preferences/shared_preferences.dart';
you can store info in sharedpreference like below
SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setInt('index', index);
then you can retrieve value like this
var index_value = prefs.getInt('index');
then you can perform your condition accordingly

Use this plugin
Add dependencies to pubspec.yaml file.
shared_preferences: ^0.5.7+3
Import shared_preferences.dart
import 'package:shared_preferences/shared_preferences.dart';
selecter() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
int index = await prefs.getInt('counter') ?? 0;
if (index == 0) {
await prefs.setInt('counter', 1;
return FirstPage();
} else {
return SecondPage();
}
}

Related

Unit-testing function with isolates and compute in flutter

I'm trying to test a widget that receives and displays some data. This widget uses a controller. In the constructor I start receiving data, after which I execute the parser in a separate isolate. During the tests, the function passed to the compute is not executed until the end, and the widget state does not change. In fact, the structure of the widget looks a little more complicated, but I wrote smaller widget that saves my problem:
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:rxdart/rxdart.dart';
class TestObj {
int id;
String name;
String number;
TestObj(this.id, this.name, this.number);
static List<TestObj> jsonListParser(String data) {
List mapObjs = json.decode(data) as List;
if (mapObjs.isEmpty) return [];
List<TestObj> testObjs = [];
for (final Map mapObj in mapObjs as List<Map>)
testObjs.add(
TestObj(
mapObj['id'] as int,
mapObj['name'] as String,
mapObj['number'] as String,
),
);
return testObjs;
}
}
class TestController {
final BehaviorSubject<List<TestObj>> testSubj;
final String responseBody =
'[{"id":2,"number":"1","name":"Объект 1"},{"id":1,"number":"2","name":"Объект 2"}]';
TestController(this.testSubj) {
getData(responseBody, testSubj);
}
Future<void> getData(
String responseBody, BehaviorSubject<List<TestObj>> testSubj) async {
List<TestObj> data = await compute(TestObj.jsonListParser, responseBody);
testSubj.sink.add(data);
}
}
class TestWidget extends StatelessWidget {
final BehaviorSubject<List<TestObj>> testSubj;
final TestController controller;
const TestWidget(this.testSubj, this.controller);
#override
Widget build(BuildContext context) {
return StreamBuilder<List<TestObj>>(
stream: testSubj.stream,
builder: (context, snapshot) => snapshot.data == null
? const CircularProgressIndicator()
: ListView.builder(
itemBuilder: (context, index) => Text(snapshot.data[index].name),
),
);
}
}
void main() {
testWidgets('example test', (tester) async {
final BehaviorSubject<List<TestObj>> testSubj =
BehaviorSubject.seeded(null);
final TestController testController = TestController(testSubj);
await tester.pumpWidget(
TestWidget(testSubj, testController),
);
expect(find.byType(CircularProgressIndicator), findsNothing);
});
}
I have tried using tester.pump, tester.pumpAndSettle (crashed by timeout) and tester.runAsync, but so far without success. What are the solutions of this problem?
As indicated in runAsync docs, it is not supported to have isolates/compute in tests that are proceeded by pump().
To make a self-contained solution, check if you run in test environment or not in your code and skip isolates when you run in a test:
import 'dart:io';
if (!kIsWeb && Platform.environment.containsKey('FLUTTER_TEST')) {
calc()
} else {
calcInIsolate()
}

Flutter - Async function not being waited for

appreciate the help! I've looked through some of the other responses on here and I can't find an answer.
I have a Provider, in which I have an async function defined. It reaches out to an external API, gets data, and then is meant to update the attributes in the Provider with the data received.
The Widget that uses the provider is meant to build a ListView with that data. projects is null until the response is received. That's why I need the async await functionality to work here. The error I'm getting says that "length can't be called on null", which means projects is still null at the time is reaches that line. That is because the async functionality isn't working.
Here is the Provider, in which my async function is defined:
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
import '../../constants/urls.dart';
import 'project.dart';
class Projects with ChangeNotifier{
List<Project> _projects;
List<Project> _myProjects;
final String authToken;
final List<Project> previousProjects;
final bool _initialLoad = true;
Projects(this.authToken, this.previousProjects);
List<Project> get projects {
return _projects;
}
List<Project> get myProjects {
return _myProjects;
}
bool get initialLoad {
return _initialLoad;
}
Future<void> fetchProjects() async {
print('inside future, a');
try {
var response = await http.get(
Uri.parse(Constants.fetchProjectsURL),
headers: {"Authorization": "Bearer " + authToken},
);
print('inside future, b');
if (response.statusCode == 200) {
final extractedData = json.decode(response.body) as List;
final List<Project> tempLoadedProjects = [];
extractedData.forEach((project) {
tempLoadedProjects.add(
Project(
// insert project params
),
);
});
_projects = tempLoadedProjects;
print(_projects);
print(projects);
notifyListeners();
} else {
print('something happened');
}
} catch (error) {
throw error;
}
}
}
Then, I used this provider in the following Widget:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../../../providers/projects/projects_provider.dart';
class ProjectsColumn extends StatelessWidget {
Future<void> fetchProjects(ctx) async {
await Provider.of<Projects>(ctx).fetchProjects();
}
Widget build(BuildContext context) {
print('Before fetch');
fetchProjects(context);
print('After fetch');
final projects = Provider.of<Projects>(context, listen: false).projects;
return ListView.builder(
itemCount: projects.length,
itemBuilder: (BuildContext ctx, int index) {
return Card(
child: Text(
'Project Name:${projects[index]}',
),
);
});
}
}
Thoughts?
You need to put await before the method to a wait, but you can't do this in build() method, So you can use future builder like the answer of #jamesdlin
or you can call fetchProjects method in intState first like this way:
class ProjectsColumn extends StatefulWidget {
#override
State<ProjectsColumn> createState() => _ProjectsColumnState();
}
class _ProjectsColumnState extends State<ProjectsColumn> {
bool _isLoading = true;
Future<void> _fetchProjects() async {
await Provider.of<Projects>(context, listen: false).fetchProjects();
_isLoading = false;
if (mounted) setState(() {});
}
#override
void initState() {
super.initState();
_fetchProjects();
}
#override
Widget build(BuildContext context) {
return _isLoading
? const Center(child: CircularProgressIndicator())
: Consumer<Projects>(
builder: (context, builder, child) => builder.projects.isEmpty
? const Center(child: Text('No Projects Found'))
: ListView.builder(
shrinkWrap: true,
itemCount: builder.projects.length,
itemBuilder: (BuildContext ctx, int index) {
return Card(
child: Text(
'Project Name:${builder.projects[index]}',
),
);
},
),
);
}
}
EDIT:
a) From the docs HERE BuildContext objects are passed to WidgetBuilder functions (such as StatelessWidget.build), and are available from the State.context member., and in the previous example I used StatefulWidget widget that extends state class, then you can use context outside build but inside the class extends state, not like StatelessWidget.
b) mounted condition, it represents whether a state is currently in the widget tree, i used it to prevent the famous error: setState() called after dispose()
see docs HERE, also this useful answer HERE

Flutter: could not find the Provider above FutureBuilder

I'm trying to navigate to a screen where depending on 3 factors:
If the app has run before or not
If the user is null or not
If the email is verified or not.
import 'package:client/providers/auth_provider.dart';
import 'package:flutter/material.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../screens/home_screen.dart';
import '../screens/login_screen.dart';
class Wrapper extends StatefulWidget {
#override
_WrapperState createState() => _WrapperState();
}
class _WrapperState extends State<Wrapper> {
FirebaseAuth auth = FirebaseAuth.instance;
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext ctx) {
return FutureBuilder(
future: hasAlreadyStarted(),
builder: (ctx, snapshot) {
return Provider.of<User>(ctx) != null &&
snapshot.data == true &&
AuthProvider().isEmailVerified
? HomeScreen()
: LoginScreen();
});
}
Future<bool> hasAlreadyStarted() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
try {
if (prefs.getBool("hasRun") == null) {
await AuthProvider().logout();
setState(() {
prefs.setBool("hasRun", true);
});
return true;
} else {
return true;
}
} catch (error) {
setState(() {
prefs.setBool("hasRun", null);
});
return false;
}
}
}
The screen works fine on it's own, but when i Navigate from a different screen it gives me the error.
Could not find the correct Provider<User> above this FutureBuilder<bool> Widget
Can you explain the error and give me a solution. Thanks :D
I just needed to wrap the entire MaterialApp with the StreamProvider

how to pass Future<int> to super class

I am trying to learn Flutter and BLoC pattern. So, I created a simple counter app. Initially count value is 0 and it increases/decreases as respective buttons are pressed. The initial value(zero) is sent to the initial state as follows.
class CounterBloc extends Bloc<CounterEvent, CounterState> {
CounterBloc() : super(CounterInitialState(0));
The Counter app worked as expected, but whenever I restart the app count starts from zero. Now I wish to start the count from where I left. I read about SharedPreferences and could successfully save the current value. But I can't load the value and send it to CounterInitialState()(The argument type 'Future' can't be assigned to the parameter type 'int'). How can I achieve this?
My counter_bloc.dart looks like this;
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:bloc/bloc.dart';
import 'package:meta/meta.dart';
import 'package:sampleOne/config/config.dart';
import 'package:shared_preferences/shared_preferences.dart';
part 'counter_event.dart';
part 'counter_state.dart';
class CounterBloc extends Bloc<CounterEvent, CounterState> {
CounterBloc() : super(CounterInitialState(0));
#override
Stream<CounterState> mapEventToState(
CounterEvent event,
) async* {
if (event is CounterIncrementEvent) {
yield* _mapIncrementEvent(event.countVal);
}
if (event is CounterDecrementEvent) {
yield* _mapDecrementEvent(event.countVal);
}
}
}
Stream<CounterState> _mapIncrementEvent(val) async* {
await saveData(val);
yield CounterIncrementState(val + 1);
}
Stream<CounterState> _mapDecrementEvent(val) async* {
await saveData(val);
yield CounterDecrementState(val + 1);
}
Future<bool> saveData(int val) async {
SharedPreferences pref = await SharedPreferences.getInstance();
return pref.setInt('key', val + 1);
}
Future<int> loadData() async {
SharedPreferences pref = await SharedPreferences.getInstance();
return pref.getInt('key') ?? 0;
}
Please help.
you need to wrap your widget with a FutureBuilder widget.
for example
FutureBuilder<int>(
future: _bloc.loadData(),
initalData: 0,
builder: (context, snapshot) {
return Text(snapshot.data.toString());
}
)
Where do you call loadData()?
Maybe you need to to put an await before like this:
Future<int> data = loadData();
int data = await loadData();
Your function is asynchronous so it returns a Future, add await to get an integer

initialRoute string is changed, but I end up at the same page regardless the initialRoute string

When using shared_preferences on flutter in main.dart in order to change the initialRoute depending on if user have seen the first page or if user is logged in I am getting the boolean which is created throughout the app and added to shared_preferences, every time I start app, I get the initialRoute string correct when debugging, but I still end up getting on the first page, regardless the conditions.
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'dart:developer';
import './pages/registration.dart';
import './pages/login_page.dart';
import './pages/confirmation.dart';
import './pages/lang_page.dart';
import './pages/main_page.dart';
import './pages/user_data.dart';
import './provider/provider.dart';
void main() => runApp(CallInfoApp());
class CallInfoApp extends StatefulWidget {
#override
_CallInfoAppState createState() => _CallInfoAppState();
}
class _CallInfoAppState extends State<CallInfoApp> {
SharedPreferences prefs;
void getSPInstance() async {
prefs = await SharedPreferences.getInstance();
}
dynamic langChosen;
dynamic isLoggedIn;
String initialRoute;
void dataGetter() async {
await getSPInstance();
setState(() {
langChosen = prefs.getBool('langChosen');
// print(langChosen);
isLoggedIn = prefs.getBool('isLoggedIn');
});
}
void getRoute() async {
await dataGetter();
debugger();
if (langChosen == true && isLoggedIn != true) {
setState(() {
initialRoute = '/login_page';
});
} else if (isLoggedIn == true) {
initialRoute = '/main_page';
} else {
setState(() {
initialRoute = '/';
});
}
}
#override
void initState() {
super.initState();
debugger();
getRoute();
}
#override
Widget build(BuildContext context) {
SystemChrome.setPreferredOrientations([
DeviceOrientation.portraitUp,
DeviceOrientation.portraitDown,
]);
return ChangeNotifierProvider<AppData>(
create: (context) => AppData(),
child: MaterialApp(
title: 'Call-INFO',
theme: ThemeData(
primarySwatch: Colors.blue,
),
initialRoute: initialRoute,
routes: {
'/': (context) => LanguagePage(),
'/registration_page': (context) => RegistrationPage(),
'/login_page': (context) => LoginPage(),
'/confirmation_page': (context) => ConfirmationPage(),
'/user_data_page': (context) => UserDataPage(),
'/main_page': (context) => MainPage(),
},
),
);
}
}
Since SharedPreference.getInstance() is an async function it will need some time until the instance is available. If you want to use it for initial route you have to make your main function async and preload it there before your MaterialApp is build.
SharedPreference prefs; //make global variable, not best practice
void main() async {
prefs = await SharedPreference.getInstance();
runApp(CallInfoApp());
}
And remove getSPInstance() from dataGetter
Also keep in midn that prefs.getBool('langChosen') will return null and not false if no entry is made into shared preference so use
langChosen = prefs.getBool('langChosen')??false;
isLoggedIn = prefs.getBool('isLoggedIn')??false;
While this solution will work it's not really good practice. I would recommend to have the initialRoute fixed to a splash screen and handle forwarding to the right page from there. A simple splash screen could look like that:
class SplashScreen extends StatefulWidget {
#override
_SplashScreenState createState() => _SplashScreenState();
}
class _SplashScreenState extends State<SplashScreen> {
#override
Widget build(BuildContext context) {
return Scaffold(body: Center(child: CircularProgressIndicator()));
}
#override
void initState() {
initSplash();
super.initState();
}
Future<void> initSplash() async {
final prefs = await SharedPreferences.getInstance();
final langChosen = prefs.getBool("lang_chosen") ?? false;
final isLoggedIn = prefs.getBool("logged_in") ?? false;
if (langChosen == true && isLoggedIn != true) {
Navigator.of(context).pushReplacementNamed('/login_page');
} else if (isLoggedIn == true) {
Navigator.of(context).pushReplacementNamed('/main_page');
} else {
Navigator.of(context).pushReplacementNamed('/');
}
}
}
Use initState to derive the data your logic is based on (i.e. fetching shared pref info). And use await keyword so that program will wait until the data is fetched from SharedPrefs. Adding the following code to class _CallInfoAppState should help
#override
void initState() {
super.initState();
dataGetter();
}
void dataGetter() async {
await getSPInstance();
setState(() {
langChosen = prefs.getBool('langChosen');
isLoggedIn = prefs.getBool('isLoggedIn');
});
}