Currently using Webviewx from https://pub.dev/packages/webviewx in my Flutter app, but unfortunately I can't seem to figure out how to avoid the player go fullscreen on my iPhone. It works fine on Android, but on ios the video keeps opening up in fullscreen instead of staying inline :(.
// Automatic FlutterFlow imports
import '../../backend/backend.dart';
import '../../flutter_flow/flutter_flow_theme.dart';
import '../../flutter_flow/flutter_flow_util.dart';
import 'index.dart'; // Imports other custom widgets
import '../actions/index.dart'; // Imports custom actions
import '../../flutter_flow/custom_functions.dart'; // Imports custom functions
import 'package:flutter/material.dart';
// Begin custom widget code
// DO NOT REMOVE OR MODIFY THE CODE ABOVE!
import 'package:webviewx/webviewx.dart';
class MuxPlayer extends StatefulWidget {
const MuxPlayer(
{Key? key,
this.width,
this.height,
required this.playbackId,
this.primaryColor})
: super(key: key);
final double? width;
final double? height;
final String playbackId;
final String? primaryColor;
#override
_MuxPlayerState createState() => _MuxPlayerState();
}
class _MuxPlayerState extends State<MuxPlayer> {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primaryColor: Colors.transparent,
),
home: VideoScreen(
pbId: widget.playbackId,
primaryColor: widget.primaryColor,
),
);
}
}
class VideoScreen extends StatelessWidget {
final String pbId;
final String? primaryColor;
const VideoScreen({Key? key, required this.pbId, this.primaryColor})
: super(key: key);
#override
Widget build(BuildContext context) {
return Container(
child: Html5Player(
playbackId: pbId,
primaryColor: primaryColor,
),
);
}
}
//
class Html5Player extends StatefulWidget {
const Html5Player({
this.height = double.maxFinite,
this.width = double.maxFinite,
required this.playbackId,
this.streamType,
this.videoTitle,
this.viewerUserId,
this.primaryColor = 'burlywood',
this.secondaryColor = '#383838',
this.muted = false,
this.crossOrigin = 'anonymous',
this.startTime,
Key? key,
}) : super(key: key);
///
/// `playbackId` is video play back id from mux.
///
final String playbackId;
///
///
///
final String? streamType;
///
///
final String? videoTitle;
///
///
///
final String? viewerUserId;
///
///
final String? primaryColor;
///
final String? secondaryColor;
///
final bool? muted;
///
final String? crossOrigin;
///
final double? startTime;
///
final double height;
///
final double width;
#override
State<Html5Player> createState() => _Html5PlayerState();
}
class _Html5PlayerState extends State<Html5Player> {
late WebViewXController webviewController;
late Widget body;
#override
void initState() {
String initialHtmlSource = '''
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Testing Mux Player</title>
</head>
<body style="overflow:hidden; margin:0">
<script src="https://unpkg.com/#mux/mux-player"></script>
<mux-player
style="height: 105vh;width: 100vw;--bottom-controls: none;--top-controls:none;--seek-backward-button: none;--seek-forward-button: none;"
addEventListener(play)
stream-type= ${widget.streamType}
playback-id= ${widget.playbackId}
metadata-video-title=${widget.videoTitle}
metadata-viewer-user-id= ${widget.viewerUserId}
primary-color= ${widget.primaryColor}
secondary-color=${widget.secondaryColor}
muted='false'
crossOrigin=${widget.crossOrigin}
forward-seek-offset="15"
backward-seek-offset="15"
start-time=${widget.startTime}
autoplay="any"
nohotkeys
loop
placeholder='#EE8B60'
playsinline
></mux-player>
</body>
</html>
''';
body = WebViewX(
height: widget.height,
width: widget.width,
initialContent: initialHtmlSource,
initialSourceType: SourceType.html,
onWebViewCreated: (controller) => webviewController = controller,
initialMediaPlaybackPolicy: AutoMediaPlaybackPolicy.alwaysAllow,
webSpecificParams: WebSpecificParams(webAllowFullscreenContent: false),
);
super.initState();
}
#override
Widget build(BuildContext context) {
return body;
}
}
Tried figuring out where to place the "allowsInlineMediaPlayback = true" property, but by the life of me I can't figure this one out. I've also tried to understand how to implement the html code in another WebView that facilitates the inline playback a bit better, but since I'm such a beginner this is proving to be extremely difficult and impossible for me to do at the moment. Any help is truly appreciated.
Related
i want to be able to call an empty variable from a class, assign a value to it and make it persistent, anything aside provider e.t.c would be help, i don't want to overhaul the entire app again to do some bloc, provider e.t.c
NB: all screens are stateful widgets
i have tried creating a class with an empty string and passing a value to it from another screen, but this doesn't seem to work
import 'package:cloud_firestore/cloud_firestore.dart';
import 'dart:async';
import 'package:firebase_auth/firebase_auth.dart';
class MethodA {
// id(user, context){
// var name =user.email;
// }
String identity;
MethodA({this.iD});
bool isLoggedIn() {
if (FirebaseAuth.instance.currentUser() != null) {
return true;
} else {
return false;
}
}
Future<void> addUserA( userinfo) async {
//this.iD=id;
Firestore.instance
.collection('user')
.document('furtherinfo').collection(identity).document('Personal Info')
.setData(userdoc)
.catchError((e) {
print(e);
});
}
each time i pass the argument to i.e foo='bar';
and i import that class in another screen, i.e screen 9, foo is automatically set to null, but i would want foo to be bar
I would suggest that you use the Provider since it is the easiest way for me to manage state throughout the app. Flutter starts with one component on top of the widget tree so i would place my provider here.
Example
void main() {runApp(MyApp());}
class MyApp extends StatelessWidget {
MyApp();
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
StreamProvider<FirebaseUser>.value(
stream: FirebaseAuth.instance.onAuthStateChanged, // Provider to manage user throughout the app.
),
],
child: MaterialApp(
title: 'My App',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primaryColor: Colors.green,
primarySwatch: Colors.green,
accentColor: Colors.yellow,
),
home: MainPage(),
),
);
}
}
Then in your class you can do the following
class MethodAService with ChangeNotifier {
String _identity = null;
FirebaseUser _user = null;
// constructor with the (new changes )
MethodAService(FirebaseUser user){
this._user = user;
}
get identity => _identity ;
setIdentity(String identity) {
_identity = identity ;
notifyListeners(); // required to notify the widgets of your change
}
}
Then when you want to use it anywhere in your app just do the following in the build method
#override
Widget build(BuildContext context) {
final user = Provider.of<FirebaseUser>(context); // to get the current user
final methodA = Provider.of<MethodAService>(context); // get your service with identity
// now you can set the string using
methodA.setIdentity('new identity');
// or just use it like this
if(methodA.identity.isNotEmpty()){
print(methodA.identity);
}else{
print('Identity is empty');
}
return ChangeNotifierProvider<MethodAService>(
builder: (context) => MethodAService(user), // Your provider to manage your object, sending the Firebase user in
child: loggedIn ? HomePage() : LoginPage(), );
}
References
Provider Package
Fireship 185 Provider
Great Youtube video explaining the code
Update for comment
For getting the user uid you can just do user.uid
Changed code above to fit the
I'm not sure put the whole app in a StreamProvider is the best choice. That means the app will be rebuilt on each stream value.
To make a Widget available on all screens, you need a TransitionBuilder in your MaterialApp.
To avoid the external dependency you can also use an InheritedWidget
signed_user.dart
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
class SignedUser extends InheritedWidget {
final FirebaseUser user;
SignedUser({#required this.user, #required Widget child})
: super(child: child);
#override
bool updateShouldNotify(SignedUser oldWidget) => true;
static SignedUser of(BuildContext context) =>
context.inheritFromWidgetOfExactType(SignedUser);
}
my_transition_builder.dart
class MyTransitionBuilder extends StatefulWidget {
final Widget child;
const MyTransitionBuilder({Key key, this.child}) : super(key: key);
#override
_MyTransitionBuilderState createState() => _MyTransitionBuilderState();
}
class _MyTransitionBuilderState extends State<MyTransitionBuilder> {
StreamBuilder<FirebaseUser> _builder;
#override
void initState() {
super.initState();
_builder = StreamBuilder<FirebaseUser>(
stream: FirebaseAuth.instance.onAuthStateChanged,
builder: (context, snapshot) {
return SignedUser(
child: widget.child,
user: snapshot.data,
);
});
}
#override
Widget build(BuildContext context) {
return _builder;
}
}
main.dart
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
// this will make your inherited widget available on all screens of your app
builder: (context, child) {
return MyTransitionBuilder(child: child);
},
routes: {
'/editAccount': (context) => new EditAccountPage(),
},
theme: ThemeData(
primarySwatch: Colors.green,
),
home: MyHomePage(),
);
}
}
usage in edit_account_page.dart
#override
Widget build(BuildContext context) {
var user = SignedUser.of(context).user;
return Scaffold(
body: FutureBuilder<DocumentSnapshot>(
future: Firestore.instance.document('users/${user.uid}').get(),
I seem to lose application state whenever I perform a hot reload.
I am using a BloC provider to store application state. This is passed at the App level in the main.dart and consumed on a child page. On the initial load of the view, the value is shown. I can navigate around the application and the state persists. However, when I perform a hot reload, I lose the values and seemingly the state.
How can I fix this issue so that state is preserved on Hot Reload?
Bloc Provider
abstract class BlocBase {
void dispose();
}
class BlocProvider<T extends BlocBase> extends StatefulWidget {
BlocProvider({
Key key,
#required this.child,
#required this.bloc,
}): super(key: key);
final T bloc;
final Widget child;
#override
_BlocProviderState<T> createState() => _BlocProviderState<T>();
static T of<T extends BlocBase>(BuildContext context){
final type = _typeOf<BlocProvider<T>>();
BlocProvider<T> provider = context.ancestorWidgetOfExactType(type);
return provider.bloc;
}
static Type _typeOf<T>() => T;
}
class _BlocProviderState<T> extends State<BlocProvider<BlocBase>>{
#override
void dispose(){
widget.bloc.dispose();
super.dispose();
}
#override
Widget build(BuildContext context){
return widget.child;
}
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return BlocProvider<ApplicationStateBloc>(
bloc: ApplicationStateBloc(),
child: MaterialApp(
title: 'Handshake',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: LoadingPage(),
)
);
}
}
class ProfileSettings extends StatefulWidget {
#override
_ProfileSettingsState createState() => _ProfileSettingsState();
}
class _ProfileSettingsState extends State<ProfileSettings>{
ApplicationStateBloc _applicationStateBloc;
#override
void initState() {
super.initState();
_applicationStateBloc = BlocProvider.of<ApplicationStateBloc>(context);
}
#override
void dispose() {
_applicationStateBloc?.dispose();
super.dispose();
}
Widget emailField() {
return StreamBuilder<UserAccount>(
stream: _applicationStateBloc.getUserAccount,
builder: (context, snapshot){
if (snapshot.hasData) {
return Text(snapshot.data.displayName, style: TextStyle(color: Color(0xFF151515), fontSize: 16.0),);
}
return Text('');
},
);
}
#override
Widget build(BuildContext context) {
return BlocProvider<ApplicationStateBloc>(
bloc: _applicationStateBloc,
child: Scaffold(
backgroundColor: Colors.white,
body: SafeArea(
child: Column(
children: <Widget>[
emailField(),
.... // rest of code
class ApplicationStateBloc extends BlocBase {
var userAccountController = BehaviorSubject<UserAccount>();
Function(UserAccount) get updateUserAccount => userAccountController.sink.add;
Stream<UserAccount> get getUserAccount => userAccountController.stream;
#override
dispose() {
userAccountController.close();
}
}
I was facing the same problem. Inherited widgets make it hard disposing bloc's resources.
Stateful widget, on the other hand, allows disposing, but in the implementation you're using it doesn't persist the bloc in the state causing state loss on widgets rebuild.
After some experimenting I came up with an approach that combines the two:
class BlocHolder<T extends BlocBase> extends StatefulWidget {
final Widget child;
final T Function() createBloc;
BlocHolder({
#required this.child,
#required this.createBloc
});
#override
_BlocHolderState createState() => _BlocHolderState();
}
class _BlocHolderState<T extends BlocBase> extends State<BlocHolder> {
T _bloc;
Function hello;
#override
void initState() {
super.initState();
_bloc = widget.createBloc();
}
#override
Widget build(BuildContext context) {
return BlocProvider(
child: widget.child,
bloc: _bloc,
);
}
#override
void dispose() {
_bloc.dispose();
super.dispose();
}
}
Bloc holder creates bloc in createState() and persists it. It also disposes bloc's resources in dispose().
class BlocProvider<T extends BlocBase> extends InheritedWidget {
final T bloc;
const BlocProvider({
Key key,
#required Widget child,
#required T bloc,
})
: assert(child != null),
bloc = bloc,
super(key: key, child: child);
static T of<T extends BlocBase>(BuildContext context) {
final provider = context.inheritFromWidgetOfExactType(BlocProvider) as BlocProvider;
return provider.bloc;
}
#override
bool updateShouldNotify(BlocProvider old) => false;
}
BlocProvider, as the name suggests, is only responsible for providing the bloc to nested widgets.
All the blocs extend BlocBase class
abstract class BlocBase {
void dispose();
}
Here's a usage example:
class RouteHome extends MaterialPageRoute<ScreenHome> {
RouteHome({List<ModelCategory> categories, int position}): super(builder:
(BuildContext ctx) => BlocHolder(
createBloc: () => BlocMain(ApiMain()),
child: ScreenHome(),
));
}
You are losing the state because your bloc is being retrieved in the _ProfileSettingsState's initState() thus, it won't change even when you hot-reload because that method is only called only once when the widget is built.
Either move it to the build() method, just before returning the BlocProvider
#override
Widget build(BuildContext context) {
_applicationStateBloc = BlocProvider.of<ApplicationStateBloc>(context);
return BlocProvider<ApplicationStateBloc>(
bloc: _applicationStateBloc,
child: Scaffold(
backgroundColor: Colors.white,
....
or to the didUpdateWidget method which is called anytime the widget state is rebuild.
Have in mind that if you are using a non-broadcast stream in your bloc you may get an exception if you try to listen to a stream that is already being listened to.
I want to users can change and save the theme color in my app. However, I have no ideas how to load the saved theme color when the app starts running. For example, I want to load the saved theme color directly in the comment place below. I tried SharedPreference. However, the SharedPreference instance needs to run with await. It seems can't be used here. Is there any way I can load the saved theme here directly instead of using setState or something like it?
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: // how to load saved theme here?
),
home: new MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
This answer goes a bit further. It shows how to load and save theme preferences, how to build a ThemeData, and how to change the theme from a page of your app.
Save the user preferences (which theme is selected) using the shared_preferences plugin.
Use the "controller pattern" that is used throughout the Flutter framework to provide the currently selected theme (and changes to it) to your app.
Use an InheritedWidget to use the controller in any part of your app.
Here is how the controller looks like:
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
/// provides the currently selected theme, saves changed theme preferences to disk
class ThemeController extends ChangeNotifier {
static const themePrefKey = 'theme';
ThemeController(this._prefs) {
// load theme from preferences on initialization
_currentTheme = _prefs.getString(themePrefKey) ?? 'light';
}
final SharedPreferences _prefs;
String _currentTheme;
/// get the current theme
String get currentTheme => _currentTheme;
void setTheme(String theme) {
_currentTheme = theme;
// notify the app that the theme was changed
notifyListeners();
// store updated theme on disk
_prefs.setString(themePrefKey, theme);
}
/// get the controller from any page of your app
static ThemeController of(BuildContext context) {
final provider = context.inheritFromWidgetOfExactType(ThemeControllerProvider) as ThemeControllerProvider;
return provider.controller;
}
}
/// provides the theme controller to any page of your app
class ThemeControllerProvider extends InheritedWidget {
const ThemeControllerProvider({Key key, this.controller, Widget child}) : super(key: key, child: child);
final ThemeController controller;
#override
bool updateShouldNotify(ThemeControllerProvider old) => controller != old.controller;
}
Here is how you would use the controller and InheritedWidget in your app:
void main() async {
// load the shared preferences from disk before the app is started
final prefs = await SharedPreferences.getInstance();
// create new theme controller, which will get the currently selected from shared preferences
final themeController = ThemeController(prefs);
runApp(MyApp(themeController: themeController));
}
class MyApp extends StatelessWidget {
final ThemeController themeController;
const MyApp({Key key, this.themeController}) : super(key: key);
#override
Widget build(BuildContext context) {
// use AnimatedBuilder to listen to theme changes (listen to ChangeNotifier)
// the app will be rebuilt when the theme changes
return AnimatedBuilder(
animation: themeController,
builder: (context, _) {
// wrap app in inherited widget to provide the ThemeController to all pages
return ThemeControllerProvider(
controller: themeController,
child: MaterialApp(
title: 'Flutter Demo',
theme: _buildCurrentTheme(),
home: MyHomePage(),
),
);
},
);
}
// build the flutter theme from the saved theme string
ThemeData _buildCurrentTheme() {
switch (themeController.currentTheme) {
case "dark":
return ThemeData(
brightness: Brightness.dark,
primarySwatch: Colors.orange,
);
case "light":
default:
return ThemeData(
brightness: Brightness.light,
primarySwatch: Colors.blue,
);
}
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: new AppBar(),
body: Center(
child: Column(
children: <Widget>[
RaisedButton(
onPressed: () {
// thanks to the inherited widget, we can access the theme controller from any page
ThemeController.of(context).setTheme('light');
},
child: Text('Light Theme'),
),
RaisedButton(
onPressed: () {
ThemeController.of(context).setTheme('dark');
},
child: Text('Dark Theme'),
)
],
),
),
);
}
}
You have a few options as to how you'd load it. The first is as Gunter said in a comment - you make MyApp into a stateful widget and load it with initState(), then setState it.
That would look something like this:
class MyApp extends StatefulWidget {
#override
MyAppState createState() => MyAppState();
}
class MyAppState extends State<MyApp> {
ThemeData theme = ThemeData.dark(); // whatever your default is
#override
void initState() {
super.initState();
SharedProperties.getInstance().then((prefs) {
ThemeData theme = ThemeData.light(); // load from prefs here
setState(() => this.theme = theme);
});
}
...
}
The second option is to use a FutureBuilder.
class MyApp extends StatelessWidget {
final Future<ThemeData> loadThemeData = SharedPreferences.getInstance().then((prefs) {
... get theme from prefs
return ThemeData.light();
});
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: loadThemeData,
builder: (context, snapshot) {
return MaterialApp(
theme: snapshot.data,
);
},
initialData: ThemeData.dark(), // whatever you want your default theme to be
);
}
}
The third option is to do the loading before you actually start your app - in your main method. I don't know if this is really recommended as if sharedpreferences takes a while it could delay the start of your app, but realistically it should be very quick and you probably want to avoid a flash different theme showing anyways.
main() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
ThemeData theme = ThemeData.dark(); // get theme from prefs
runApp(MyApp(
theme: theme,
));
}
class MyApp extends StatelessWidget {
final ThemeData theme;
const MyApp({Key key, #required this.theme}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: theme,
....
);
}
}
Load theme data from local storage in main function as await
I managed to pass Stateful class variables' values to the State class through constructor like below:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Demo',
home: MyHomePage('John', 'Morison'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage(this.fname, this.lname);
final String fname;
final String lname;
#override
_MyHomePageState createState() => _MyHomePageState(fname, lname);
}
class _MyHomePageState extends State<MyHomePage> {
_MyHomePageState(this.fname, this.lname);
final String fname;
final String lname;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Text('Hello $fname $lname'),
)
);
}
}
That's weird and I had to do lot of work as there is more than two variables. Is there a better way?
Yes, there is widget:
From Doc:
/// The current configuration.
///
/// A [State] object's configuration is the corresponding [StatefulWidget]
/// instance. This property is initialized by the framework before calling
/// [initState]. If the parent updates this location in the tree to a new
/// widget with the same [runtimeType] and [Widget.key] as the current
/// configuration, the framework will update this property to refer to the new
/// widget and then call [didUpdateWidget], passing the old configuration as
/// an argument.
T get widget => _widget;
T _widget;
Code should look like below:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Demo',
home: MyHomePage('John', 'Morison'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage(this.fname, this.lname);
final String fname;
final String lname;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Text('Hello ${widget.fname} ${widget.lname}'),
)
);
}
}
How do I scrape a web page that generates some of its content with a JavaScript element? It is a local court booking site for my squash club and from what I can see, this is the JavaScript script it calls to retrieve the court bookings:
function load_js(url) {
var e = document.createElement("script");
e.src = url;
e.type = "text/javascript";
if (document.getElementsByTagName("head")[0].lastChild.src == e.src) {
document.getElementsByTagName("head")[0].replaceChild(e, document.getElementsByTagName("head")[0].lastChild);
}
else {
document.getElementsByTagName("head")[0].appendChild(e);
}
}
onload = function () {
setInterval("load_js('/js/bookings_reload.js.php')", 30000);
};
How could I replicate this in Flutter? I can scrape the HTML and parse it to create an HTML document, but since Flutter disallows the use of the dart:html library, I can't create a ScriptElement to duplicate the JavaScript script. Is there an alternative in Flutter?
Here is my Dart function so far:
var httpClient = createHttpClient();
getData() async {
// Send POST request to get authorized cookie
var response = await httpClient.post('http://bookings.squashgym.co.nz/login',
body: {'username': username, 'password': password});
// Send get request with authenticated cookie to get bookings
var courts = await httpClient.get('http://bookings.squashgym.co.nz/booking-sheet',
headers: {'cookie': response.headers['set-cookie']});
Document data = parse(courts.body);
httpClient.close();
}
Thanks!
I agree on using WebView to load the URL needed. Here is an example implementation of WebView using the webview_flutter.
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
import 'dart:async';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final Completer<WebViewController> _controller =
Completer<WebViewController>();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: WebView(
initialUrl: "https://flutter.dev/",
onWebViewCreated: (WebViewController webViewController) {
_controller.complete(webViewController);
},
javascriptMode: JavascriptMode.unrestricted,
),
);
}
}
This is how it will look like:
And when the page is rendered you will be able to get the DOM from the WebView. There is also an available example of getting DOM from WebView. Check it here.