Count page transitions in Flutter using iframes [flutter web] - flutter

I would like to include another website in my own website.
For that I would like to register a callback to track site-tranitions (i.e. the user clicks on a link on the embedded site and is redirected to a different url / sub-url (?).) I currently use IFrameElement to embed a site, this would in theory allow to register event listeners, but I cannot find any documentation about that.
My main goal is to count the number of page transitions. This is my current code:
import 'package:flutter/material.dart';
import 'package:wikipoker/widgets/my_iframe.dart';
import 'package:wikipoker/widgets/player_tab.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Wikipedia Poker',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(title: 'Game of Wikipedia Poker'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
buildIFrame(constraints.maxHeight, constraints.maxWidth),
],
);
},
),
);
}
String _youtube = 'https://www.youtube.com/embed/RQzhAQlg2JQ';
String _wiki = 'https://de.wikipedia.org/wiki/Hunde';
Widget buildIFrame(double height, double width) {
return Column(
children: [
IFrameWidget(
_wiki,
height,
width * (4 / 5),
),
],
);
}
}
import 'dart:html';
import 'dart:ui' as ui;
import 'package:flutter/cupertino.dart';
class IFrameWidget extends StatefulWidget {
final String _url;
double _height = 500;
double _width = 500;
IFrameWidget(this._url, this._height, this._width);
#override
State<StatefulWidget> createState() => _IFrameWidgetState();
}
class _IFrameWidgetState extends State<IFrameWidget> {
Widget _iframeWidget;
#override
void initState() {
super.initState();
final IFrameElement _iframeElement = IFrameElement();
// _iframeElement.height = '500';
// _iframeElement.width = '500';
// FIXME This does not load.
// _iframeElement.addEventListener('onLoad', (event) {
// setState(() {
// _iframeWidget = Text("Lol");
// });
// });
_iframeElement.src = widget._url;
_iframeElement.style.border = 'none';
// ignore: undefined_prefixed_name
ui.platformViewRegistry.registerViewFactory(
'iframeElement',
(int viewId) => _iframeElement,
);
_iframeWidget = HtmlElementView(
key: UniqueKey(),
viewType: 'iframeElement',
);
}
#override
Widget build(BuildContext context) {
return SizedBox(
height: widget._height,
width: widget._width,
child: _iframeWidget,
);
}
}
The IFrameElement has some fields and methods, which look like they could be useful.
addEventListener expects a type of event, but there is no overview about what that might be.
The documentation is very incomplete for this and I have no idea which event I would like to register.
My hope is, that I can use events from the native html iframe for that.
Documentation for IFrames: https://api.flutter.dev/flutter/dart-html/IFrameElement-class.html

Old question, but I hope the answer will help someone looking for a solution:
here is described very well
Note: need to restart the IDE (at least mine refused to work without restart)

Related

Flutter - Accessing a Provider from a higher point in the Widget tree

I've been working with Flutter recently, and I saw that there was many ways to deal with state management.
Following the recommendations there, I've been using Provider to deal with the state of my app.
I can update a part of my state from one of the widgets in my UI. To do that, I can call a method of the provider that's above the current widget in the context. No problems with this.
But I want the update of my state to be made from an overlay.
The issue is: When I'm inserting an OverlayEntry with Overlay.of(context)?.insert(), it inserts the overlayEntry to the closest Overlay, which is in general the root of the app, which is above the ChangeProvider. As a result, I get an exception saying I can't find the Provider from the OverlayEntry.
Here is a replication code I've been writting:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: ChangeNotifierProvider(
create: (context) => NumberModel(), // All widgets that will be lower in the widget tree will have access to NumberModel
child: NumberDisplayer()
),
);
}
}
// Simple ChangeNotifier. We have a number that we can increment.
class NumberModel extends ChangeNotifier {
int _number = 10;
int get number => _number;
void add_one() {
_number = number + 1;
notifyListeners();
}
}
// This class displays a number, and a button.
class NumberDisplayer extends StatelessWidget {
NumberDisplayer({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
var overlayEntry = OverlayEntry(builder: (context) =>
Positioned(
top: 100,
left: 50,
child: FloatingActionButton(onPressed: (){
// Throws "Error: Could not find the correct Provider<NumberModel> above this _OverlayEntryWidget Widget"
Provider.of<NumberModel>(context, listen: false).add_one();
})));
return Consumer<NumberModel>(
builder: (context, numberModel, child) {
return Column(
children: [
Text('Number: ${numberModel.number}'),
FloatingActionButton(onPressed: () {
Overlay.of(context)?.insert(overlayEntry);
})
],
);
},
);
}
}
I would like to find a way to update the information in my provider from the overlay, but I'm not sure how to approach this problem.
Thanks for your help everyone !

Preserve state and prevent initState of been called more than once

I have 3 page (all statefull widgets) :
Home page
Weather page
Setting page
The things is when i'm going from home page to weather page with a "Navigator.pushNamed" and going from the weather page to home page with a "Navigator.pop", the next time i'm trying to go to the weather page from the home page, initState method is called again...
How i can manage to make it call only the first time and not been called every time i push into the weather page ?
Here my app.dart code :
import 'package:exomind/src/core/views/home_view.dart';
import 'package:exomind/src/features/weather/presentation/views/weather_view.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import '../injection_container.dart';
import 'core/styles/colors.dart';
import 'features/settings/presentation/bloc/settings_bloc.dart';
import 'features/settings/presentation/views/settings_view.dart';
import 'features/weather/presentation/bloc/weather_bloc.dart';
/// The Widget that configures your application.
class MyApp extends StatelessWidget {
const MyApp({
Key? key,
}) : super(key: key);
#override
Widget build(BuildContext context) {
// Glue the SettingsController to the MaterialApp.
//
// The AnimatedBuilder Widget listens to the SettingsController for changes.
// Whenever the user updates their settings, the MaterialApp is rebuilt.
return MultiBlocProvider(
providers: [
BlocProvider<WeatherBloc>(
create: (_) => serviceLocator<WeatherBloc>()),
BlocProvider<SettingsBloc>(
create: (_) => serviceLocator<SettingsBloc>()
..add(
const SettingsLoaded(),
)),
],
child:
BlocBuilder<SettingsBloc, SettingsState>(builder: (context, state) {
return MaterialApp(
debugShowCheckedModeBanner: false,
// Providing a restorationScopeId allows the Navigator built by the
// MaterialApp to restore the navigation stack when a user leaves and
// returns to the app after it has been killed while running in the
// background.
restorationScopeId: 'app',
// Provide the generated AppLocalizations to the MaterialApp. This
// allows descendant Widgets to display the correct translations
// depending on the user's locale.
localizationsDelegates: const [
AppLocalizations.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: const [
Locale('en', ''), // English, no country code
],
// Use AppLocalizations to configure the correct application title
// depending on the user's locale.
//
// The appTitle is defined in .arb files found in the localization
// directory.
onGenerateTitle: (BuildContext context) =>
AppLocalizations.of(context)!.appTitle,
// Define a light and dark color theme. Then, read the user's
// preferred ThemeMode (light, dark, or system default) from the
// SettingsController to display the correct theme.
theme:
ThemeData(fontFamily: 'Circular', primaryColor: kPrimaryColor),
darkTheme: ThemeData.dark(),
themeMode: state.themeMode,
// Define a function to handle named routes in order to support
// Flutter web url navigation and deep linking.
onGenerateRoute: (RouteSettings routeSettings) {
return MaterialPageRoute<void>(
settings: routeSettings,
builder: (BuildContext context) {
switch (routeSettings.name) {
case SettingsView.routeName:
return const SettingsView();
case WeatherView.routeName:
return const WeatherView();
case HomeView.routeName:
return const HomeView();
default:
return const HomeView();
}
},
);
},
);
}));
}
}
Here my home_view.dart code :
import 'package:flutter/material.dart';
import '../../features/weather/presentation/views/weather_view.dart';
class HomeView extends StatefulWidget {
const HomeView({Key? key}) : super(key: key);
static const routeName = '/home';
#override
State<HomeView> createState() => _HomeViewState();
}
class _HomeViewState extends State<HomeView>
with SingleTickerProviderStateMixin {
late AnimationController rotationController;
#override
void initState() {
rotationController =
AnimationController(duration: const Duration(seconds: 1), vsync: this)
..repeat();
super.initState();
}
#override
void dispose() {
rotationController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
final double height = MediaQuery.of(context).size.height;
final double width = MediaQuery.of(context).size.width;
return Scaffold(
body: Stack(
alignment: Alignment.center,
children: [
Positioned(
top: (height / 2),
child: RotationTransition(
turns: Tween(begin: 0.0, end: 1.0).animate(rotationController),
child: IconButton(
icon: const Icon(Icons.wb_sunny),
color: Colors.yellow,
iconSize: (width * 0.2),
onPressed: () {
Navigator.of(context).pushNamed(WeatherView.routeName);
},
),
),
)
],
),
);
}
}
Here my weather_view.dart code :
import 'dart:async';
import 'package:exomind/src/features/weather/presentation/bloc/weather_bloc.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:percent_indicator/percent_indicator.dart';
class WeatherView extends StatefulWidget {
const WeatherView({Key? key}) : super(key: key);
static const routeName = '/weather';
#override
State<WeatherView> createState() => _WeatherViewState();
}
class _WeatherViewState extends State<WeatherView>
with SingleTickerProviderStateMixin {
#override
void initState() {
print("initcalled")
super.initState();
}
#override
void dispose() {
rotationController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
super.build(context);
final double width = MediaQuery.of(context).size.width;
final double height = MediaQuery.of(context).size.height;
return Scaffold();
}
}
Any help and explanation would be appreciate :)
I can't think of a "clean" way of not executing the initState in _WeatherViewState. Are you trying to avoid the same city added to the WeatherBloc more than once? If so, I'd check for the existence of 'city' in the WeatherBloc before adding.
In your onGenerateRoute you call the WeatherView constructor each time:
case WeatherView.routeName:
return const WeatherView();
This in turn will call initState. What you need to do is create the WeatherView page widget once and use it in the onGenerateRoute:
final _weatherView = const WeatherView();
In your onGenerateRoute:
case WeatherView.routeName:
return _weatherView;
As #RoslanAmir said there is no way to prevent initstate of been called each time we push into a statefulwidget.
So to prevent my event of being added into my bloc each time we push into the stateful widget i add a bool variable to each state to know if the event should be added or not again.
For those who want a precise answer don't hesitate.
Just add a parameter to the Weather page: a boolean that specifies if the rebuild is true or false. (If true, it will call the initState())
This code works fine.
class WeatherView extends StatefulWidget {
final bool rebuild;
static const routeName = '/weather';
WeatherView({
Key? key,
required this.rebuild,
}) : super(key: key);
#override
State<WeatherView> createState() => _WeatherViewState();
}
and the WeatherViewState's initState() will be:
#override
void initState() {
if (widget.rebuild) {
print("initcalled");
super.initState();
} else {
print("Not called");
}
}
So, in your app.dart you should now route to the page by doing
case WeatherView.routeName:
return const WeatherView(rebuild: true); //Choose if rebuild or not by true and false

How to sequentially iterate through object values of a Map in Flutter and to return each value one by one as you iterate?

Map<String, VideoPlayerController> controllers = {
'one${1 + 1}': VideoPlayerController.asset('assets/videos/6.mp4'),
'one${1 + 2}': VideoPlayerController.asset('assets/videos/2.mp4'),
'one${1 + 3}': VideoPlayerController.asset('assets/videos/3.mp4'),
'one${1 + 4}': VideoPlayerController.asset('assets/videos/4.mp4'),
'one${1 + 6}': VideoPlayerController.asset('assets/videos/1.mp4'),
'one${1 + 7}': VideoPlayerController.asset('assets/videos/7.mp4'),
'one${1 + 8}': VideoPlayerController.asset('assets/videos/8.mp4'),
};
controllerLooper3() {
for (value in controllers.values) {
return value;
}
}
I am trying to iterate and return each value of the Map/to access each value sequentially. However, when I try to loop and return the value I am only getting the first value of the object. How do I do it so that it loops through the the value data but without changing that data into a string type.That is each objects value is maintained without changing its raw make up. That is I do not want to print it I just want a mechanism that is accessing the values sequentially one by one and returning each value one by one as it iterates.
The below is a demo using the video player plugin. Instead of a Map, per Remi's recommendation in the comments to your question above, I created a basic video model with title and controller properties and added them to a List. It's much easier to use a List (with proper models) if you're looking to make a YouTube-like feed (most likely using a ListView or CustomScrollView). Ensure your pubspec.yaml is updated with your video assets (and change the below filenames), and the example should work for you. The listeners below will print when the video is initialized and the duration.
import 'package:flutter/material.dart';
import 'package:video_player/video_player.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Video Player Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Video Player Demo'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
List<VideoModel> _controllers = [];
#override
void initState() {
super.initState();
_controllers = [
VideoModel(
title: 'Alpha',
controller: VideoPlayerController.asset('assets/videos/small.mp4')),
VideoModel(
title: 'Beta',
controller: VideoPlayerController.asset('assets/videos/small.mp4')),
VideoModel(
title: 'Gamma',
controller: VideoPlayerController.asset('assets/videos/small.mp4')),
];
_controllerLooper();
}
_controllerLooper() {
for (VideoModel video in _controllers) {
final listener = () {
if (video.controller.value.initialized) {
print('${video.title} - video initialized');
}
print('${video.title} duration: ${video.controller.value.position}');
};
video.controller
..addListener(listener)
..setVolume(1.0)
..setLooping(true)
..initialize();
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: ListView.builder(
itemCount: _controllers.length,
itemBuilder: (context, index) {
final controller = _controllers[index].controller;
return GestureDetector(
onTap: () {
if (controller.value.isPlaying) {
controller.pause();
} else if (!controller.value.isPlaying) {
controller.play();
}
},
child: AspectRatio(
aspectRatio: 16.0 / 9.0,
child: VideoPlayer(_controllers[index].controller),
),
);
}),
);
}
}
class VideoModel {
VideoModel({this.title, this.controller});
final String title;
final VideoPlayerController controller;
}

Flutter using inherited widgets with Navigator returning NoSuchMethodError

I'm making a flutter app. I have a homepage widget which shows two things
- device code;
temperature
At first, these are set to some default values, but the user then goes from the home page to a new route, call this widget the sensor widget. In this new page, it basically connects to a sensor. This sensor sends out the device code and temperature, and I am able to show it inthe sensor widget.
The problem comes when i want to show this new information onthe homepage. I made a button where the user can go back to the homepage, but I want a way to update the homepage with the values I have in my sensor widget.
Im making use of the InheritedWidget class to make this happen, but I keep getting a null error when I try to access the variables in the homepage.
Below is the inherited widget class for this.
class TemperatureContext extends InheritedWidget {
final int _deviceCode;
final double _temperature;
int get deviceCode => _deviceCode;
double get temperature => _temperature;
set deviceCode(int d) {_deviceCode = d;}
set temperature(double t) {_temperature = t}
TemperatureContext(this.deviceCode, this.temperature, {Key key, Widget child})
.super(key: key, child:child)
#override
bool updateShouldNotify(Widget oldWidget) {
return (temperature != oldWidget.temperature && deviceCode != oldWidget.deviceCode) }
static TemperatureContext of(BuildContext context) {
return context.inheritFromWidgetOfExactType(TemperatureContext) }
}
I then have the homepage, new_widget is a function that builds a widget based on the
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
static int deviceCode = 0;
static double deviceCode = get_temp_globally();
#override
Widget build(BuildContext context) {
final tempContext = TemperatureContext.of(context);
Widget output = new MaterialApp(
home: new Scaffold(
appBar: new AppBar(
title: new Text('Homepage'),
),
body: new Column(
children: <Widget>[
new_widget(tempContext.deviceCode, tempContext.temperature),
new FlatButton(
child: new Text('Set new sensor'),
onPressed: () {
Navigator.of(context).pushNamed('/ChangePage');
})
],
)));
return output;
}
Next is the change page widget where the user is taken to when they press the button in the home page
class SensorWidget extends StatefulWidget {
SensorWidget({Key key, this.title}) : super(key: key);
final String title;
#override
_SensorWidgetState createState() => new _SensorWidgetState();
}
class _SensorWidgetState extends State<SensorWidget> {
static int deviceCode = 0;
static double temperature = get_temp_globally();
/* Some code that gets the deviceCode,
temperature and sets them to the above
variables */
#override
Widget build(BuildContext context) {
output = TemperatureContext(
deviceCode,
temperature,
child: MaterialApp(
home: new Scaffold(
appBar: new AppBar(
title: const Text('Sensor widget'),
actions: _buildActionButtons(),
),
floatingActionButton: _buildScanningButton(),
body: new Container(
child: new FlatButton(
child: new Text("Go back"),
onPressed: () {
Navigator.of(context).pop(true);
}
),
),
),
),
);
return output;
}
}
And this is my main.dart file
void main() {
runApp(new MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Temperature detector',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new HomePage(),
routes: <String, WidgetBuilder> {
'/HomePage' : (BuildContext context) => new HomePage(),
'/SensorWidget': (BuildContext context) => new SensorWidget(),
},
);
}
}
Basically when I put the new_widget function in my HomePage class (which I didnt put here, but basically builds a widget based on the two arguements provided), I get a "NoSuchMethodError": the getter deviceCode was called on null.
I dont get why this is null since I already initialized it. Any help? Thanks
See https://flutter.io/flutter-for-android/#what-is-the-equivalent-of-startactivityforresult :
The Navigator class handles routing in Flutter and is used to get a result back from a route that you have pushed on the stack. This is done by awaiting on the Future returned by push().
For example, to start a location route that lets the user select their location, you could do the following:
Map coordinates = await Navigator.of(context).pushNamed('/location');
And then, inside your location route, once the user has selected their location you can pop the stack with the result:
Navigator.of(context).pop({"lat":43.821757,"long":-79.226392});

how to keep the state of my widgets after scrolling?

I'm codeing an app with flutter an i'm haveing problems with the development. I'm trying to have a listview with a custom widget that it has a favourite icon that represents that you have liked it product. I pass a boolean on the constructor to set a variables that controls if the icons is full or empty. When i click on it i change it state. It works awesome but when i scroll down and up again it loses the lastest state and returns to the initial state.
Do you know how to keep it states after scrolling?
Ty a lot <3
Here is my code:
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(widget.title),
),
body: new ListView.builder(
itemCount: 100,
itemBuilder: (BuildContext context, int index){
return new LikeClass(liked: false);
},
),
);
}
}
class LikeClass extends StatefulWidget {
final bool liked;//i want this variable controls how heart looks like
LikeClass({this.liked});
#override
_LikeClassState createState() => new _LikeClassState();
}
class _LikeClassState extends State<LikeClass> {
bool liked;
#override
void initState() {
liked=widget.liked;
}
#override
Widget build(BuildContext context) {
return new Container(
child: new Column(
children: <Widget>[
new GestureDetector(
onTap:((){
setState(() {
liked=!liked;
//widget.liked=!widget.liked;
});
}),
child: new Icon(Icons.favorite, size: 24.0,
color: liked?Colors.red:Colors.grey,
//color: widget.liked?Colors.red:Colors.grey,//final method to control the appearance
),
),
],
),
);
}
}
You have to store the state (favorite or not) in a parent widget. The ListView.builder widget creates and destroys items on demand, and the state is discarded when the item is destroyed. That means the list items should always be stateless widgets.
Here is an example with interactivity:
class Item {
Item({this.name, this.isFavorite});
String name;
bool isFavorite;
}
class MyList extends StatefulWidget {
#override
State<StatefulWidget> createState() => MyListState();
}
class MyListState extends State<MyList> {
List<Item> items;
#override
void initState() {
super.initState();
// Generate example items
items = List<Item>();
for (int i = 0; i < 100; i++) {
items.add(Item(
name: 'Item $i',
isFavorite: false,
));
}
}
#override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
return ListItem(
items[index],
() => onFavoritePressed(index),
);
},
);
}
onFavoritePressed(int index) {
final item = items[index];
setState(() {
item.isFavorite = !item.isFavorite;
});
}
}
class ListItem extends StatelessWidget {
ListItem(this.item, this.onFavoritePressed);
final Item item;
final VoidCallback onFavoritePressed;
#override
Widget build(BuildContext context) {
return ListTile(
title: Text(item.name),
leading: IconButton(
icon: Icon(item.isFavorite ? Icons.favorite : Icons.favorite_border),
onPressed: onFavoritePressed,
),
);
}
}
If you don't have many items in the ListView you can replace it with a SingleChildScrollview and a Column so that the Widgets aren't recycled. But it sounds like you should have a list of items where each item has an isFavourite property, and control the icon based on that property. Don't forget to setState when toggling the favorite.
Other answer are better for your case but this an alternative and can be used if you want to only keep several elements alive during a scroll. In this case you can use AutomaticKeepAliveClientMixin with keepAlive.
class Foo extends StatefulWidget {
#override
FooState createState() {
return new FooState();
}
}
class FooState extends State<Foo> with AutomaticKeepAliveClientMixin {
bool shouldBeKeptAlive = false;
#override
Widget build(BuildContext context) {
super.build(context);
shouldBeKeptAlive = someCondition();
return Container(
);
}
#override
bool get wantKeepAlive => shouldBeKeptAlive;
}
ListView.builder & GridView.builder makes items on demand. That means ,they construct item widgets & destroy them when they going beyond more than cacheExtent.
So you cannot keep any ephemeral state inside that item widgets.(So most of time item widgets are Stateless, but when you need to use keepAlive you use Stateful item widgets.
In this case you have to keep your state in a parent widget.So i think the best option you can use is State management approach for this. (like provider package, or scoped model).
Below link has similar Example i see in flutter.dev
Link for Example
Hope this answer will help for you
A problem with what you are doing is that when you change the liked variable, it exists in the Widget state and nowhere else. ListView items share Widgets so that only a little more than are visible at one time are created no matter how many actual items are in the data.
For a solution, keep a list of items as part of your home page's state that you can populate and refresh with real data. Then each of your LikedClass instances holds a reference to one of the actual list items and manipulates its data. Doing it this way only redraws only the LikedClass when it is tapped instead of the whole ListView.
class MyData {
bool liked = false;
}
class _MyHomePageState extends State<MyHomePage> {
List<MyData> list;
_MyHomePageState() {
// TODO use real data.
list = List<MyData>();
for (var i = 0; i < 100; i++) list.add(MyData());
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(widget.title),
),
body: new ListView.builder(
itemCount: list.length,
itemBuilder: (BuildContext context, int index) {
return new LikeClass(list[index]);
},
),
);
}
}
class LikeClass extends StatefulWidget {
final MyData data;
LikeClass(this.data);
#override
_LikeClassState createState() => new _LikeClassState();
}
class _LikeClassState extends State<LikeClass> {
#override
Widget build(BuildContext context) {
return new Container(
child: new Column(
children: <Widget>[
new GestureDetector(
onTap: (() {
setState(() {
widget.data.liked = !widget.data.liked;
});
}),
child: new Icon(
Icons.favorite,
size: 24.0,
color: widget.data.liked ? Colors.red : Colors.grey,
),
),
],
),
);
}
}