I'm really new in flutter and in Cubit pattern.
As far as I know Cubit is something quite new and now it is the base of the BloC pattern.
I' have tried to understand how it works and I have understood some concept and something about the state management and I have tried to build a simple app.
The app is connected with a API that respond with a list of Shops and have a BottomTabBar.
This is my code:
main.dart
import 'package:myapp/cubit/maison_cubit.dart';
import 'package:myapp/pages/maison.dart';
import 'package:myapp/pages/home.dart';
import 'package:myapp/repository/maisons_repository.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:google_fonts/google_fonts.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
final textTheme = Theme.of(context).textTheme;
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'My Cubit app',
home: BlocProvider(
create: (context) => MaisonCubit(MaisonsRepository()),
child: HomePage(),
),
routes: {
MaisonPage.routeName: (ctx) => MaisonPage(),
},
);
}
}
My maison_repository.dart just call an external API and add the maisons to a list.
import 'dart:convert';
import 'package:myapp/const.dart';
import 'package:http/http.dart' as http;
import '../models/maison.dart';
class MaisonsRepository {
List<Maison> items = [];
Future<List<Maison>> getMaisons() async {
final response = await http.get('https://get-maison.example', headers: {
"Accept": "application/json",
"content-type": "application/json",
});
if (response.statusCode == 200) {
items.clear();
List<dynamic> list = json.decode(response.body);
list.forEach((element) {
items.add(Maison.fromJson(element));
});
return items;
} else {
throw Exception('Failed to load maisons');
}
}
Maison find(String id) {
return items.firstWhere((element) => element.id == id);
}
}
This is home.dart. In the MaisonLoaded I call BottomBar that need to build the bottom bar and display the correct page. In all the docs I have read I haven't found a good explaination about how to manage the data after I get it from the repository, so I have added a constructor in my BottomBar and I have passed the data. Is it correct?
import 'package:myapp/bottom_bar.dart';
import 'package:myapp/cubit/maison_cubit.dart';
import 'package:myapp/pages/maisons_listing.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
#override
Widget build(BuildContext context) {
final maisonCubit = context.bloc<MaisonCubit>();
maisonCubit.getMaisons();
return BlocConsumer<MaisonCubit, MaisonState>(
listener: (context, state) {
if (state is MaisonError) {
return Container(
child: Text('Missing connection'),
);
}
},
builder: (context, state) {
if (state is MaisonLoading) {
return Container(
child: Text('My loader here'),
);
}
if (state is MaisonLoaded) {
return Container(
child: BottomBar(state.maisons),
);
}
return Container();
},
);
}
}
This is the BottomBar widget ( MapPage is just a container with a text in it )
import 'package:myapp/models/maison.dart';
import 'package:myapp/pages/maisons_listing.dart';
import 'package:myapp/pages/map.dart';
import 'package:flutter/material.dart';
class BottomBar extends StatefulWidget {
final List<Maison> maisons;
BottomBar(this.maisons);
#override
_BottomBarState createState() => _BottomBarState();
}
class _BottomBarState extends State<BottomBar> {
int _currentIndex = 0;
void onTabTapped(int index) {
setState(() {
_currentIndex = index;
});
}
#override
Widget build(BuildContext context) {
List<Widget> _children = [
MaisonListingPage(widget.maisons),
MapPage(widget.maisons),
];
return Scaffold(
body: _children[_currentIndex],
bottomNavigationBar: BottomNavigationBar(
backgroundColor: Color(0xFFDDCDC8),
currentIndex: _currentIndex,
onTap: onTabTapped,
items: [
BottomNavigationBarItem(
icon: Icon(Icons.list),
title: Text('Maisons'),
),
BottomNavigationBarItem(
icon: Icon(
Icons.map,
),
title: Text('Map'),
),
],
),
);
}
}
And this is the page that should display the maison's list.
import 'package:myapp/models/maison.dart';
import 'package:myapp/pages/maison.dart';
import 'package:flutter/material.dart';
class MaisonListingPage extends StatefulWidget {
final List<Maison> maisons;
MaisonListingPage(this.maisons);
#override
_MaisonListingPageState createState() => _MaisonListingPageState();
}
class _MaisonListingPageState extends State<MaisonListingPage> {
List<Maison> _currentList = [];
List<Maison> _maisons = [];
#override
Widget build(BuildContext context) {
_maisons = widget.maisons;
return SingleChildScrollView(
child: Column(
children: <Widget>[
HeroWidget(),
Transform.translate(
offset: Offset(0, -30),
child: Container(
height: 60.0,
padding: EdgeInsets.symmetric(vertical: 5.0),
margin: EdgeInsets.symmetric(horizontal: 16),
decoration: BoxDecoration(
color: Colors.white,
boxShadow: [
BoxShadow(
color: Colors.grey[350],
blurRadius: 20.0,
offset: Offset(0, 10.0),
),
],
),
child: ListTile(
leading: Icon(Icons.search),
title: TextField(
decoration: InputDecoration(
border: InputBorder.none,
hintText: AppLocalizations.of(context)
.translate('home_search_input_placeholder'),
),
),
trailing: IconButton(
icon: Icon(
Icons.clear,
),
onPressed: () {
},
),
),
),
),
MediaQuery.removePadding(
context: context,
removeTop: true,
child: maisonListView(),
),
],
),
);
}
ListView maisonListView() {
return ListView.builder(
shrinkWrap: true,
primary: false,
itemCount: _currentList.length,
itemBuilder: (BuildContext context, int index) {
return GestureDetector(
onTap: () => Navigator.of(context).pushNamed(
MaisonPage.routeName,
arguments: _currentList[index].id,
),
child: Text(_currentList[index].name),
);
},
);
}
onChange() {
setState(() {});
}
}
If I run the code, I can see the list of the maison. The problem comes with the tap on a single maison, I'd like to open a new page and show all the content.
So I have added a method in the maison_repository, if you check it you can see a find method.
In the single maison page I have tried to init the repository and use the find method in this way:
import 'dart:async';
import 'package:myapp/models/maison.dart';
import 'package:myapp/repository/maisons_repository.dart';
import 'package:flutter/material.dart';
class MaisonPage extends StatefulWidget {
static const routeName = '/single-maison';
#override
_MaisonPageState createState() => _MaisonPageState();
}
class _MaisonPageState extends State<MaisonPage> {
MaisonsRepository _maisonsRepository;
#override
Widget build(BuildContext context) {
String maisonId = ModalRoute.of(context).settings.arguments as String;
Maison maison = _maisonsRepository.find(maisonId);
return Scaffold(
body: CustomScrollView(
slivers: <Widget>[
SliverAppBar(
expandedHeight: MediaQuery.of(context).size.width * 0.70,
pinned: true,
flexibleSpace: FlexibleSpaceBar(
title: Text(maison.name),
background: MaisonHeroWidget(
id: maison.id,
imageUrl: maison.imageUrl,
),
),
),
SliverList(
delegate: SliverChildListDelegate(
[
Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: <Widget>[
Text(
maison.description,
),
],
),
),
],
),
),
],
),
);
}
}
When I try to visit this page I get:
The following NoSuchMethodError was thrown building MaisonPage(dirty, dependencies: [_ModalScopeStatus], state: _MaisonPageState#335d9):
The method 'find' was called on null.
Receiver: null
Tried calling: find("2389")
The relevant error-causing widget was:
MaisonPage file:///Users/christiangiupponi/Dev/FlutterApp/myapp/lib/main.dart:63:40
When the exception was thrown, this was the stack:
#0 Object.noSuchMethod (dart:core-patch/object_patch.dart:53:5)
#1 _MaisonPageState.build (package:myapp/pages/maison.dart:69:40)
#2 StatefulElement.build (package:flutter/src/widgets/framework.dart:4619:28)
#3 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:4502:15)
#4 StatefulElement.performRebuild (package:flutter/src/widgets/framework.dart:4675:11)
...
════════════════════════════════════════════════════════════════════════════════════════════════════
What am I missing?
Is this a good approach to get the data?
As mentioned in the comments, _maisonsRepository is yet to be initialized. As for the empty items List, you can call getMaisons when the repository has been initialized.
Related
This is my main
import 'package:flutter/material.dart';
import 'package:gestionchamp/screen_intro/view/onboarding_screen.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'auth/main_page.dart';
import 'pages/login_page.dart';
import 'package:firebase_core/firebase_core.dart';
bool show = true;
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
final prefs = await SharedPreferences.getInstance();
show = prefs.getBool('ON_BOARDING') ?? true;
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({
Key? key,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData(
textTheme: const TextTheme(
headline1: TextStyle(
fontSize: 25,
color: Colors.green,
fontWeight: FontWeight.bold,
),
headline2: TextStyle(
fontSize: 18,
color: Colors.green,
fontWeight: FontWeight.w400,
wordSpacing: 1.2,
height: 1.2),
headline3: TextStyle(
fontSize: 18,
color: Colors.green,
fontWeight: FontWeight.bold,
),
headline4: TextStyle(
fontSize: 18,
color: Colors.white,
fontWeight: FontWeight.bold,
),
)),
home: show ? OnBoardingScreen() : const MainPage(),
);
}
}
The mainPage
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import '../pages/home_page.dart';
import '../pages/login_page.dart';
import 'auth_page.dart';
class MainPage extends StatelessWidget {
const MainPage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: StreamBuilder<User?>(
stream: FirebaseAuth.instance.authStateChanges(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return HomePage();
} else {
return AuthPage();
}
},
),
);
}
}
and this is the Home page
import 'dart:io';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:flutter_spinkit/flutter_spinkit.dart';
import 'package:provider/provider.dart';
import 'package:gestionchamp/mqtt/MQTTAppState.dart';
import 'package:gestionchamp/mqtt/MQTTManager.dart';
class HomePage extends StatefulWidget {
const HomePage({Key? key}) : super(key: key);
#override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
final TextEditingController _hostTextController = TextEditingController();
final TextEditingController _messageTextController = TextEditingController();
final TextEditingController _topicTextController = TextEditingController();
late MQTTAppState currentAppState;
late MQTTManager manager;
#override
void initState() {
super.initState();
/*
_hostTextController.addListener(_printLatestValue);
_messageTextController.addListener(_printLatestValue);
_topicTextController.addListener(_printLatestValue);
*/
}
#override
void dispose() {
_hostTextController.dispose();
_messageTextController.dispose();
_topicTextController.dispose();
super.dispose();
}
final user = FirebaseAuth.instance.currentUser!;
// document IDs
List<String> docIDs = [];
//get docIDs
Future getDocId() async {
await FirebaseFirestore.instance.collection('users').get().then(
(snapshot) => snapshot.docs.forEach(
(document) {
print(document.reference);
docIDs.add(document.reference.id);
},
),
);
}
#override
Widget build(BuildContext context) {
final MQTTAppState appState = Provider.of<MQTTAppState>(context);
// Keep a reference to the app state.
currentAppState = appState;
final Scaffold scaffold = Scaffold(body: _buildColumn());
return scaffold;
}
Widget _buildAppBar(BuildContext context) {
return AppBar(
title: const Text('MQTT'),
backgroundColor: Colors.greenAccent,
);
}
Widget _buildColumn() {
return Column(
children: <Widget>[
_buildConnectionStateText(
_prepareStateMessageFrom(currentAppState.getAppConnectionState)),
_buildEditableColumn(),
_buildScrollableTextWith(currentAppState.getHistoryText)
],
);
}
Widget _buildEditableColumn() {
return Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
children: <Widget>[
_buildTextFieldWith(
_hostTextController,
'Entrez l\'adresse du broker',
currentAppState.getAppConnectionState),
const SizedBox(height: 10),
_buildTextFieldWith(
_topicTextController,
'Entrez un topic pour souscrire ou mettre en écoute',
currentAppState.getAppConnectionState),
const SizedBox(height: 10),
_buildPublishMessageRow(),
const SizedBox(height: 10),
_buildConnecteButtonFrom(currentAppState.getAppConnectionState)
],
),
);
}
Widget _buildPublishMessageRow() {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Expanded(
child: _buildTextFieldWith(_messageTextController,
'Entrez un message', currentAppState.getAppConnectionState),
),
_buildSendButtonFrom(currentAppState.getAppConnectionState)
],
);
}
Widget _buildConnectionStateText(String status) {
return Row(
children: <Widget>[
Expanded(
child: Container(
color: Colors.deepOrangeAccent,
child: Text(status, textAlign: TextAlign.center)),
),
],
);
}
Widget _buildTextFieldWith(TextEditingController controller, String hintText,
MQTTAppConnectionState state) {
bool shouldEnable = false;
if (controller == _messageTextController &&
state == MQTTAppConnectionState.connected) {
shouldEnable = true;
} else if ((controller == _hostTextController &&
state == MQTTAppConnectionState.disconnected) ||
(controller == _topicTextController &&
state == MQTTAppConnectionState.disconnected)) {
shouldEnable = true;
}
return TextField(
enabled: shouldEnable,
controller: controller,
decoration: InputDecoration(
contentPadding:
const EdgeInsets.only(left: 0, bottom: 0, top: 0, right: 0),
labelText: hintText,
));
}
Widget _buildScrollableTextWith(String text) {
return Padding(
padding: const EdgeInsets.all(20.0),
child: Container(
width: 400,
height: 200,
child: SingleChildScrollView(
child: Text(text),
),
),
);
}
Widget _buildConnecteButtonFrom(MQTTAppConnectionState state) {
return Row(
children: <Widget>[
Expanded(
// ignore: deprecated_member_use
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.lightBlueAccent),
child: const Text('Connect'),
onPressed: state == MQTTAppConnectionState.disconnected
? _configureAndConnect
: null, //
),
),
const SizedBox(width: 10),
Expanded(
// ignore: deprecated_member_use
child: ElevatedButton(
style: ElevatedButton.styleFrom(backgroundColor: Colors.redAccent),
child: const Text('Disconnect'),
onPressed: state == MQTTAppConnectionState.connected
? _disconnect
: null, //
),
),
],
);
}
Widget _buildSendButtonFrom(MQTTAppConnectionState state) {
// ignore: deprecated_member_use
return ElevatedButton(
style: ElevatedButton.styleFrom(backgroundColor: Colors.green),
child: const Text('Send'),
onPressed: state == MQTTAppConnectionState.connected
? () {
_publishMessage(_messageTextController.text);
}
: null, //
);
}
// Fonctions utiles
String _prepareStateMessageFrom(MQTTAppConnectionState state) {
switch (state) {
case MQTTAppConnectionState.connected:
return 'Connected';
case MQTTAppConnectionState.connecting:
return 'Connecting';
case MQTTAppConnectionState.disconnected:
return 'Disconnected';
}
}
void _configureAndConnect() {
// ignore: flutter_style_todos
// TODO: Use UUID
String osPrefix = 'IoT';
if (Platform.isAndroid) {
osPrefix = 'Android ';
}
MQTTManager manager = MQTTManager(
host: _hostTextController.text,
topic: _topicTextController.text,
identifier: osPrefix,
state: currentAppState);
manager.initializeMQTTClient();
manager.connect();
}
void _disconnect() {
manager.disconnect();
}
void _publishMessage(String text) {
String osPrefix = 'IoT';
if (Platform.isAndroid) {
osPrefix = 'Android';
}
final String message = osPrefix + ' a dit: ' + text;
manager.publish(message);
_messageTextController.clear();
}
}
and this is the debug output
Error: Could not find the correct Provider<MQTTAppState> above this HomePage Widget
This happens because you used a `BuildContext` that does not include the provider
of your choice. There are a few common scenarios:
- You added a new provider in your `main.dart` and performed a hot-reload.
To fix, perform a hot-restart.
- The provider you are trying to read is in a different route.
Providers are "scoped". So if you insert of provider inside a route, then
other routes will not be able to access that provider.
- You used a `BuildContext` that is an ancestor of the provider you are trying to read.
Make sure that HomePage is under your MultiProvider/Provider<MQTTAppState>.
This usually happens when you are creating a provider and trying to read it immediately.
The provider you're trying to call isn't identified at the place you're referring it. For this you need to provide it in the root widget or above the widget tree where you want to call it.
You could wrap your MaterialApp by that provider like this:
Provider< MQTTAppState>(
create: (_) => MQTTAppState(),
child: MaterialApp(),
),
And please change the title of the post to the one that describes your problem.
You have to put the provider somewhere in the widget tree, higher up than where you want to use it. I'm using a MultiProvider with which you can put several providers, but just one is also fine!
Something like this:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:my_app/screens/welcome_screen.dart';
import 'provider_updates.dart'; // The location of the MyFirstProvider class
void main() async {
WidgetsFlutterBinding.ensureInitialized();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
print('Building $runtimeType');
MyFirstProvider myFirstProvider = MyFirstProvider(); // Create your provider here!
return MultiProvider(
providers: [
ChangeNotifierProvider<MyFirstProvider>(
create: (context) => myFirstProvider), // Use it here. Now, it will exist in the entire app!
// You can put more providers here
],
builder: (context, child) {
return MaterialApp(
home: WelcomeScreen(myThemeClass),
);
});
}
}
I'm trying to build a Widget test for a screen that's using the Provider framework.
The app has 1 screen with 1 button, when I tap the button it will trigger a function to update the state. Once the state is updated a string with the key Key('LoadedString') will appear.
When manually testing in the simulator this works as expected.
When I'm trying to use a widget test to verify the expected behavior it seems like the UI is not updating.
This is the Widget:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'provider_page_state_controller.dart';
class ProviderPageWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MultiProvider(providers: [
ChangeNotifierProvider<ProviderPageStateController>(
create: (context) => ProviderPageStateController())
], child: HomePage());
}
}
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
var screenHeight = MediaQuery.of(context).size.height;
ScreenState state =
Provider.of<ProviderPageStateController>(context).pageState;
// Loading state
if (state == ScreenState.Loading) {
return Scaffold(
body: Container(
color: Colors.purple,
child: Center(child: Text('Loading...')),
));
}
// SuccessfullyLoaded state
if (state == ScreenState.SuccessfullyLoaded) {
return Scaffold(
body: Container(
color: Colors.green,
child: Center(
child: Column(
children: [
Padding(
padding:
EdgeInsets.fromLTRB(0, (screenHeight / 2) - 100, 0, 0),
),
Text('Loaded!', key: Key('LoadedString')),
Container(
margin: EdgeInsets.all(25),
color: Colors.blueAccent,
child: TextButton(
child: Text(
'Reset',
style: TextStyle(color: Colors.white, fontSize: 20.0),
),
onPressed: () {
Provider.of<ProviderPageStateController>(context,
listen: false)
.updateState(ScreenState.InitialState);
},
),
),
],
))));
}
// InitialState (=default state)
return Scaffold(
body: Container(
color: Colors.white,
child: Center(
child: Column(
children: [
Padding(
padding: EdgeInsets.fromLTRB(0, (screenHeight / 2) - 100, 0, 0),
),
Container(
key: Key('ButtonA'),
margin: EdgeInsets.all(25),
color: Colors.blueAccent,
child: TextButton(
child: Text(
'Button A',
style: TextStyle(color: Colors.white, fontSize: 20.0),
),
onPressed: () {
Provider.of<ProviderPageStateController>(context,
listen: false)
.functionA();
},
),
),
],
),
),
));
}
}
This is the Controller:
import 'package:flutter/material.dart';
enum ScreenState { InitialState, Loading, SuccessfullyLoaded }
class ProviderPageStateController extends ChangeNotifier {
var pageState = ScreenState.InitialState;
void updateState(ScreenState screenState) {
pageState = screenState;
notifyListeners();
}
Future<void> functionA() async {
updateState(ScreenState.Loading);
Future.delayed(Duration(milliseconds: 1000), () {
updateState(ScreenState.SuccessfullyLoaded);
});
}
Future<void> reset() async {
updateState(ScreenState.InitialState);
}
}
This is the Widget test:
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:provider/provider.dart';
import '../provider_page/provider_page_state_controller.dart';
import '../provider_page/provider_page_widget.dart';
void main() {
testWidgets('ProviderPageWidget SuccessfullyLoaded test',
(WidgetTester tester) async {
final providerPageStateController = ProviderPageStateController();
await tester.pumpWidget(
ListenableProvider<ProviderPageStateController>.value(
value: providerPageStateController,
child: MaterialApp(
home: ProviderPageWidget(),
),
),
);
await providerPageStateController.functionA();
expect(providerPageStateController.pageState, ScreenState.Loading);
await tester.pumpAndSettle(Duration(seconds: 2));
expect(
providerPageStateController.pageState, ScreenState.SuccessfullyLoaded);
// Why does this assert fail?
expect(find.byKey(Key('LoadedString')), findsOneWidget);
});
}
Why is the last expect of my Widget test failing?
Use the Provider.value constructor, since you are using a variable instead of instantiating an object. And to make sure all dependants are updated eagerly use the the lazy: false flag in tests, even when the value is not accessed. The resulting changes are:
void main() {
final providerPageState = ProviderPageState();
...
Provider<ProviderPageState>.value(
value: providerPageState,
child: MaterialApp(
home: ProviderPageWidget(),
),
lazy: false,
),
);
I would also recommend switching to ChangeNotifierProvider, when using provider to store states. Make sure your ProviderPageState is extending ChangeNotifier and implementing the needed methods, as reference you can use the implementation of ValueNotifier.
I found out what the issue was. I was not pumping the correct widget in the widget test.
Updating the widget test to the following solved the issue:
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:provider/provider.dart';
import '../provider_page/provider_page_state_controller.dart';
import '../provider_page/provider_page_widget.dart';
void main() {
testWidgets('ProviderPageWidget SuccessfullyLoaded test',
(WidgetTester tester) async {
final providerPageStateController = ProviderPageStateController();
await tester.pumpWidget(
ListenableProvider<ProviderPageStateController>.value(
value: providerPageStateController,
child: MaterialApp(
home: HomePage(),
),
),
);
await providerPageStateController.functionA();
expect(providerPageStateController.pageState, ScreenState.Loading);
await tester.pumpAndSettle(Duration(seconds: 2));
expect(providerPageStateController.pageState, ScreenState.SuccessfullyLoaded);
// Why does this assert fail?
expect(find.byKey(Key('LoadedString')), findsOneWidget);
});
}
The change is on the line:
home: HomePage(),
The following error occurred while cloning Instagram. If the if statement is removed, the output is normal, but if an if statement is added, an error occurs. I can't get a hold of it at all.
The error message as below.
Class 'bool' has no instance method 'call'.
Receiver: true
Tried calling: call()
My code is below.
import 'package:flutter/material.dart';
import './style.dart' as style;
import 'package:http/http.dart' as http;
import 'dart:convert';
void main() {
runApp(
MaterialApp(
debugShowCheckedModeBanner: false,
theme: style.theme,
home: MyApp(),
),
);
}
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
#override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
var tab = 1;
var data = [];
getData() async {
var result = await http.get(
Uri.parse('https://codingapple1.github.io/app/data.json'),
);
var result2 = jsonDecode(result.body); //jsonDecode(): json을 list나 map자료로 변환
setState(() {
data = result2;
});
}
#override
void initState() {
super.initState();
getData();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(
'Instagram',
),
actions: [
IconButton(
icon: Icon(Icons.add_box_outlined),
onPressed: () {},
iconSize: 30,
),
],
),
body: Home(data: data),
bottomNavigationBar: BottomNavigationBar(
showSelectedLabels: false,
showUnselectedLabels: false,
onTap: (i) {
setState(() {
tab = i;
});
},
items: [
BottomNavigationBarItem(icon: Icon(Icons.home_outlined), label: '홈'),
BottomNavigationBarItem(
icon: Icon(Icons.shopping_bag_outlined), label: '샵'),
],
),
);
}
}
class Home extends StatelessWidget {
const Home({Key? key, this.data}) : super(key: key);
final data;
#override
Widget build(BuildContext context) {
if (data.isNotEmpty()) {
return ListView.builder(
itemCount: 3,
itemBuilder: (context, index) {
return SizedBox(
height: 380,
child: Column(
children: [
Image.network(data[index]['image'], width: 300),
SizedBox(
width: 300,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'좋아요 ${data[index]['likes']}',
style: TextStyle(fontWeight: FontWeight.bold),
),
Text(data[index]['user']),
Text(data[index]['content']),
],
),
)
],
),
);
},
);
} else {
return Text('로딩중임');
}
}
}
Class 'bool' has no instance method 'call'. Receiver: true
isNotEmpty is a property, not a method, replace isNotEmpty() to isNotEmpty and will work fine
First delete this lines because we will call getData into futureBuilder
#override
void initState() {
super.initState();
getData();
}
then make your listview.builder into futureBuilder with getData() function
data with snapshot and replace isNotEmpty() with hasData like the code below
FutureBuilder(
future: getData(),
builder: ((context, AsyncSnapshot<dynamic> snapshot) {
if (snapshot.data.hasData) {
return ListView.builder(
itemCount: 3,
itemBuilder: (context, index) {
return SizedBox(
height: 380,
child: Column(
children: [
Image.network(snapshot.data[index]['image'], width: 300),
SizedBox(
width: 300,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'좋아요 ${snapshot.data[index]['likes']}',
style: TextStyle(fontWeight: FontWeight.bold),
),
Text(snapshot.data[index]['user']),
Text(snapshot.data[index]['content']),
],
),
)
],
),
);
},
);
} else {
return Text('로딩중임');
}
}
I have been trying to build a News App that fetches data from the newsapi.org service and just when I am about to call the data inside the main method I am getting this error saying that my class 'NewsModel' isn't of the type 'Widget' as required by the closure's context. I have no idea what that means but here is my code for the app split into 2 files.
import 'package:flutter/material.dart';
import 'models/news_model.dart';
import 'news_service.dart';
import 'package:assgn_digia_tech/models/news_model.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
bool _loading = true;
var newsList;
List<NewsModel> articles = [];
void getNews() async {
newsService apiNews = newsService();
await apiNews.getNews();
articles = apiNews.apiNews;
setState(() {
_loading = false;
});
}
#override
void initState() {
super.initState();
getNews();
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
appBar: AppBar(
centerTitle: true,
title: Text(
'News API',
style: TextStyle(
color: Colors.black,
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
backgroundColor: Colors.cyan[50],
// ignore: prefer_const_literals_to_create_immutables
actions: [
Padding(
padding: const EdgeInsets.only(right: 12.0),
child: IconButton(
icon: Icon(Icons.search, color: Colors.black, size: 26),
onPressed: () {},
),
),
],
),
body: SafeArea(
child: _loading
? Center(
child: CircularProgressIndicator(),
)
: SingleChildScrollView(
child: Container(
child: Column(
children: [
Container(
child: ListView.builder(
itemCount: articles.length,
shrinkWrap: true,
itemBuilder: (context, index) {
return NewsModel(
title: articles[index].title,
description: articles[index].description,
author: articles[index].author,
content: articles[index].content,
urlToImage: articles[index].urlToImage,
);
},
),
),
],
),
),
),
),
),
);
}
}
import 'dart:convert';
import 'package:assgn_digia_tech/models/news_model.dart';
import 'package:http/http.dart' as http;
class newsService {
List<NewsModel> apiNews = [];
Future<void> getNews() async {
String apiUrl =
'https://newsapi.org/v2/top-headlines?country=in&apiKey=4e3474bb91ec49eda31b75e2daf6da3c';
var response = await http.get(Uri.parse(apiUrl));
var jsonData = jsonDecode(response.body);
if (response.statusCode == 200) {
jsonData['articles'].forEach((element) {
if (element['urlToImage'] != null && element['description'] != null) {
NewsModel article = NewsModel(
title: element['title'],
author: element['author'],
description: element['description'],
urlToImage: element['urlToImage'],
content: element["content"],
);
apiNews.add(article);
}
});
}
}
}
itemBuilder: (context, index) {
return NewsModel(
You are supposed to return a Widget from the builder, because the purpose is to build a UI. Do you have a custom "NewsWidget" here, or do you want to build it from scratch? Maybe start by returning Text(articles[index].title) and then building it up from there to include all the other parts of your NewsModel.
i'm trying to implement provider in my flutter app but getting a little stuck.
It's basically a list of tasks. You should be able to double tap an existing task and edit the text. Below the task text is a counter which shows how long the task text is but it never updates when I type. Any ideas why?
task.dart
class Task {
String taskText;
bool completed;
String id = UniqueKey().toString();
Task({
this.taskText = '',
this.completed = false,
});
void toggle() {
completed = !completed;
}
}
task_data.dart
import 'dart:collection';
import 'package:flutter/foundation.dart';
import 'package:task_management/model/task.dart';
class TaskData with ChangeNotifier {
List<Task> _tasks = [];
UnmodifiableListView<Task> get tasks => UnmodifiableListView(_tasks);
void addTask(Task task) {
int index = _tasks.indexWhere((element) => element.id == task.id);
if (index == -1) {
_tasks.add(task);
} else {
_tasks[index] = task;
}
notifyListeners();
}
void toggleTask(Task task) {
task.toggle();
notifyListeners();
}
void removeTask(Task task) {
_tasks.remove(task);
notifyListeners();
}
}
main.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'model/task_data.dart';
import 'screens/home.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (context) => TaskData(),
child: MaterialApp(
title: 'Provider Demo',
theme: ThemeData(
primarySwatch: Colors.green,
),
home: Home(),
),
);
}
}
home.dart
import 'package:flutter/material.dart';
import 'package:task_management/screens/task_list.dart';
import 'task_form.dart';
class Home extends StatelessWidget {
// create the appbar
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('GeeksforGeeks'),
),
body: Container(
padding: EdgeInsets.all(20),
child: TaskList(),
),
floatingActionButton: FloatingActionButton(
child: Icon(
Icons.add,
),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => TaskForm(
taskIndex: -1,
)));
},
),
);
}
}
task_list.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:task_management/model/task_data.dart';
import 'task_form.dart';
class TaskList extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Consumer<TaskData>(builder: (context, data, child) {
return ListView.builder(
scrollDirection: Axis.vertical,
shrinkWrap: true,
itemCount: data.tasks.length,
itemBuilder: (context, index) {
final task = data.tasks[index];
// gesture detection
return GestureDetector(
onLongPress: () => data.removeTask(task),
onDoubleTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => TaskForm(
taskIndex: index,
)));
},
child: Container(
margin: EdgeInsets.only(bottom: 10),
padding: EdgeInsets.fromLTRB(12, 5, 8, 5),
width: double.infinity,
decoration: BoxDecoration(color: Colors.black12, borderRadius: BorderRadius.circular(8)),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
// text field
Text(
task.taskText,
style: TextStyle(decoration: task.completed ? TextDecoration.lineThrough : null, fontSize: 16, fontWeight: FontWeight.bold),
),
// switch case
Switch(
value: task.completed,
onChanged: (c) => data.toggleTask(task),
),
],
),
),
);
},
);
});
}
}
task_form.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:task_management/model/task.dart';
import 'package:task_management/model/task_data.dart';
class TaskForm extends StatelessWidget {
final int taskIndex;
TaskForm({required this.taskIndex});
#override
Widget build(BuildContext context) {
TaskData taskProvider = Provider.of<TaskData>(context);
Task task = taskIndex != -1 ? taskProvider.tasks[taskIndex] : Task();
return Scaffold(
appBar: AppBar(
title: Text('Task Form'),
),
body: Container(
padding: EdgeInsets.all(18),
child: Column(
children: [
TextFormField(
initialValue: task.taskText,
onChanged: (c) => task.taskText = c,
),
Text(task.taskText.length.toString()),
// add button
ElevatedButton(
child: Text(
'Submit',
),
// assign action
onPressed: () {
Provider.of<TaskData>(context, listen: false).addTask(task);
Navigator.pop(context);
},
)
],
),
),
);
}
}
This happened as the Task object not notifying the changes,
Try
TextFormField(
initialValue: task.taskText,
onChanged: (c) {
task.taskText = c,
Provider.of<TaskData>(context, listen: false).notifyListeners();
}
),