Stacking Widgets Over InAppWebView - flutter

I have a page that displays an InAppWebView. I want to stack a widget on top of it. When I open the page, it displays the stacked widget for a split second but once the web view loads, the stacked widget disappears. I opened the flutter inspector and the widget does exist on the page, it just looks like it is hidden underneath the web view. Do I need to take a different approach here when stacking widgets on top of the InAppWebView?
class UnityFormViewPage extends StatefulWidget {
final UnityForm _selectedForm;
UnityFormViewPage(this._selectedForm);
#override
_UnityFormViewPageState createState() => _UnityFormViewPageState();
}
class _UnityFormViewPageState extends State<UnityFormViewPage> {
String currentUrl = '';
#override
Widget build(BuildContext context) {
return Material(
child: Scaffold(
appBar: AppBar(
title: Text(widget._selectedForm.title),
leading: IconButton(
icon: Icon(Icons.arrow_back),
onPressed: () {
Navigator.pop(context);
}),
),
body: Stack(
children: [
OfflineBar(),
InAppWebView(
initialUrl: widget._selectedForm.formUrl,
onLoadStart: (InAppWebViewController controller, String url) {
setState(() {
this.currentUrl = url;
});
},
),
],
)),
);
}
}

You can copy paste run full code below
You can change sequence and use Positioned to position OfflineBar
code snippet
Stack(
children: [
InAppWebView(
initialUrl: widget._selectedForm.formUrl,
onLoadStart: (InAppWebViewController controller, String url) {
setState(() {
this.currentUrl = url;
});
},
),
Positioned(left: 0, right: 0, top: 20, child: OfflineBar()),
],
)
working demo
full code
import 'package:flutter/material.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
class UnityForm {
String title;
String formUrl;
UnityForm({this.title, this.formUrl});
}
class UnityFormViewPage extends StatefulWidget {
final UnityForm _selectedForm;
UnityFormViewPage(this._selectedForm);
#override
_UnityFormViewPageState createState() => _UnityFormViewPageState();
}
class _UnityFormViewPageState extends State<UnityFormViewPage> {
String currentUrl = '';
#override
Widget build(BuildContext context) {
return Material(
child: Scaffold(
appBar: AppBar(
title: Text(widget._selectedForm.title),
leading: IconButton(
icon: Icon(Icons.arrow_back),
onPressed: () {
Navigator.pop(context);
}),
),
body: Stack(
children: [
InAppWebView(
initialUrl: widget._selectedForm.formUrl,
onLoadStart: (InAppWebViewController controller, String url) {
setState(() {
this.currentUrl = url;
});
},
),
Positioned(left: 0, right: 0, top: 20, child: OfflineBar()),
],
)),
);
}
}
class OfflineBar extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container(color: Colors.purple, child: Text("OfflineBar"));
}
}
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: UnityFormViewPage(
UnityForm(title: "test", formUrl: "https://flutter.dev/")),
);
}
}

Related

Make bottomNavigationBar expand down to use whole screen in Flutter

I am new to Flutter and went on to do the codelabs - first flutter app
Since I'm learning Flutter to develop mobile apps, this tutorials use of NavigationRail isn't too good looking on a phone. I tried to switch it out for a BottomNavigationBar. When changing the background color of the navbar I noticed it doesnt expand to use the full screen. Is it always like this, or is there something making it display it this way in the code?Could'nt find any useful information about this case.
Is it possible to make the green background cover the, here black, area at the bottom of the screen?
Area under bar, white when debugging on real device, here it is black
The final code from the tutorial is poorly adjusted to:
import 'package:english_words/english_words.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (context) => MyAppState(),
child: MaterialApp(
title: 'Namer App',
theme: ThemeData(
useMaterial3: true,
colorScheme: ColorScheme.fromSeed(seedColor: Colors.green),
),
home: MyHomePage(),
),
);
}
}
class MyAppState extends ChangeNotifier {
var current = WordPair.random();
void getNext() {
current = WordPair.random();
notifyListeners();
}
var favorites = <WordPair>[];
void toggleFavorite() {
if (favorites.contains(current)) {
favorites.remove(current);
} else {
favorites.add(current);
}
notifyListeners();
}
}
class MyHomePage extends StatefulWidget {
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
var selectedIndex = 0;
#override
Widget build(BuildContext context) {
Widget page;
switch(selectedIndex){
case 0:
page = GeneratorPage();
break;
case 1:
page = FavoritesPage();
break;
default:
throw UnimplementedError('no widget for $selectedIndex');
}
return LayoutBuilder(
builder: (context, constraints) {
return Scaffold(
body: Center(
child: page,
),
bottomNavigationBar: BottomNavigationBar (
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.home),
label: 'Home',
),
BottomNavigationBarItem(
icon: Icon(Icons.favorite),
label: 'Favorites',
),
],
currentIndex: selectedIndex,
onTap: _onItemTapped,
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
elevation: 0.0,
),
);
}
);
}
void _onItemTapped(int index){
setState(() {
selectedIndex = index;
});
}
}
class FavoritesPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
var appState = context.watch<MyAppState>();
if (appState.favorites.isEmpty) {
return Center(
child: Text('No favorites yet.'),
);
}
return ListView(
children: [
Padding(
padding: const EdgeInsets.all(20),
child: Text('You have '
'${appState.favorites.length} favorites:'),
),
for (var pair in appState.favorites)
ListTile(
leading: Icon(Icons.favorite),
title: Text(pair.asLowerCase),
),
],
);
}
}
class GeneratorPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
var appState = context.watch<MyAppState>();
var pair = appState.current;
IconData icon;
if (appState.favorites.contains(pair)) {
icon = Icons.favorite;
} else {
icon = Icons.favorite_border;
}
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
BigCard(pair: pair),
SizedBox(height: 10),
Row(
mainAxisSize: MainAxisSize.min,
children: [
ElevatedButton.icon(
onPressed: () {
appState.toggleFavorite();
},
icon: Icon(icon),
label: Text('Like'),
),
SizedBox(width: 10),
ElevatedButton(
onPressed: () {
appState.getNext();
},
child: Text('Next'),
),
],
),
],
),
);
}
}
class BigCard extends StatelessWidget {
const BigCard({
Key? key,
required this.pair,
}) : super(key: key);
final WordPair pair;
#override
Widget build(BuildContext context) {
var theme = Theme.of(context);
var style = theme.textTheme.displayMedium!.copyWith(
color: theme.colorScheme.onPrimary,
);
return Card(
color: theme.colorScheme.primary,
elevation: 10,
child: Padding(
padding: const EdgeInsets.all(20),
child: Text(pair.asLowerCase, style: style),
),
);
}
}
Tried changing elevation to 0.0, expandbody and what not. Nothing seems to be working here?
You can use SystemUiOverlayStyle class
class _MyHomePageState extends State<MyHomePage> {
#override
void initState() {
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle.light
.copyWith(systemNavigationBarColor: Colors.greenAccent));
super.initState();
}

Flutter how to load new screen by tap on navigation bar

I have created a custom bottom navigation bar for my app but I messed up my code. Right now its just shifting screen by true false value. I want to load screen but what I done is simple showing screen in body by bool.
My code
bottomNavigationBar: CustomBottomNavigationBar(
iconList: [
'images/ichome.png',
'images/icservice.png',
'images/icstore.png',
'images/Component 7 – 1#2x.png',
],
iconList2: [
'images/ichomeactive.png',
'images/icserviceactive.png',
'images/icstoreactive.png',
'images/icaccount.png',
],
onChange: (val) {
setState(() {
_selectedItem = val;
print(val);
if (val == 0) {
setState(() {
home = true;
service = false;
shop = false;
account = false;
});
}
if (val == 1) {
home = false;
service = true;
shop = false;
account = false;
}
if (val == 2) {
home = false;
service = false;
shop = true;
account = false;
}
if (val == 3) {
home = false;
service = false;
shop = false;
account = true;
}
});
},
defaultSelectedIndex: 0,
),
You can see on click I am changing bool value and in body show my widget. I know its wrong I do very stupid thing. That's why I need to know how I can load the page instead of just show and hide ? Also I need to show the navigation bar also on each page.
Please refer below code of Navigation bar
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: SettingView(),
);
}
}
class SettingView extends StatefulWidget {
#override
_SettingViewState createState() => _SettingViewState();
}
class _SettingViewState extends State<SettingView> {
final tabs = [DashboardView(), NotificationView(), ProfileView()];
int _currentIndex = 0;
#override
void initState() {
setState(() {});
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
toolbarHeight: 40.0,
elevation: 0,
centerTitle: true,
backgroundColor: Colors.blue,
title: Text("Navigation Bar"),
),
bottomNavigationBar: BottomNavigationBar(
backgroundColor: Colors.blue,
currentIndex: _currentIndex,
type: BottomNavigationBarType.fixed,
selectedItemColor: Colors.white,
unselectedItemColor: Colors.white.withOpacity(0.5),
items: [
BottomNavigationBarItem(
icon: InkResponse(
focusColor: Colors.transparent,
hoverColor: Colors.transparent,
highlightColor: Colors.transparent,
child: Container(
padding: EdgeInsets.only(
left: 10,
),
child: Icon(
Icons.dashboard,
),
),
),
title: Padding(padding: EdgeInsets.zero),
backgroundColor: Colors.blue,
),
BottomNavigationBarItem(
icon: Container(
padding: EdgeInsets.only(
right: 10,
),
child: Icon(Icons.notifications),
),
title: Padding(padding: EdgeInsets.zero),
backgroundColor: Colors.blue,
),
BottomNavigationBarItem(
icon: Container(
padding: EdgeInsets.only(
right: 10,
),
child: Icon(Icons.account_box),
),
title: Padding(padding: EdgeInsets.zero),
backgroundColor: Colors.blue,
)
],
onTap: (index) {
setState(() {
_currentIndex = index;
});
},
),
body: tabs[_currentIndex],
);
}
}
/*Dashboard*/
class DashboardView extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container(
child: Center(
child: Text("Dashboard"),
),
);
}
}
/*Notification*/
class NotificationView extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container(
child: Center(
child: Text("Notification"),
),
);
}
}
/*Profile*/
class ProfileView extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container(
child: Center(
child: Text("Profile"),
),
);
}
}
You can do something like that:
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'app name',
home: HomeScreen(),
routes: <String, WidgetBuilder>{
'/route1': (BuildContext context) => FirstScreen(),
'/route2': (BuildContext context) => SecondScreen(),
},
);
}
Create reusable navigation bar Widget and for selected content just tell navigator where it needs to bring you:
Navigator.pushNamed(context, '/route1');

Flutter load webview inside the fragment

// Here is my flutter code
import 'package:flutter/material.dart';
import 'package:flutter_webview_plugin/flutter_webview_plugin.dart';
class HairtipsPage extends StatefulWidget {
#override
_HairtipsPageState createState() => _HairtipsPageState();
}
class _HairtipsPageState extends State<HairtipsPage> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child : WebviewScaffold(
url: "https://www.google.com",
appBar: new AppBar(
// title: new Text('Hairtips'),
),
withZoom: true,
withLocalStorage: true,
)
),
);
}
}
I am using bottom navigation in my app and trying to implement webview inside the fragment.i know how to acheive the same in android also i dont want the webview should open in a browser.i am expecting the webview should load inside the app and within the fragment.
You can use the Flutter webview plugin. Here is the URL for the plugin https://pub.dartlang.org/packages/webview_flutter
The webview will load inside the App with CircularProgressIndicator.
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
class WebView extends StatefulWidget {
#override
_WebViewState createState() => _WebViewState();
}
class _WebViewState extends State<WebView> {
final Completer<WebViewController> _controller =
Completer<WebViewController>();
num _stackToView = 1;
void _handleLoad(String value) {
setState(() {
_stackToView = 0;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: Builder(builder: (BuildContext context) {
return IconButton(
icon: Icon(Icons.volume_up, color: Colors.black,),
onPressed: () {
Navigator.pop(context);
},
);
}),
backgroundColor: Colors.white10,
elevation: 0,
),
body: IndexedStack(
index: _stackToView,
children: [
Column(
children: <Widget>[
Expanded(
child: WebView(
initialUrl: "https://www.google.co.in/",
javascriptMode: JavascriptMode.unrestricted,
onPageFinished: _handleLoad,
onWebViewCreated: (WebViewController webViewController) {
_controller.complete(webViewController);
},
)),
],
),
Container(
child: Center(child: CircularProgressIndicator(),)
),
],
));
}
}

Flutter BottomNavigationBar and advanced navigation

I'm building an app with 3 items in the bottom navigation bar. When I change the tab, a different widget is rendering. So far, so good...
import 'package:flutter/material.dart';
class BottomTest extends StatefulWidget {
State createState() => new _BottomTestState();
}
class _BottomTestState extends State<BottomTest> {
List<Widget> _pages;
Widget _selectedContent;
int _bottomIndex;
#override
void initState() {
_bottomIndex = 0;
super.initState();
}
#override
Widget build(BuildContext context) {
_definePages();
return Scaffold(
appBar: AppBar(
title: Text('Bottom Navigation Test'),
),
body: _selectedContent ?? _pages[_bottomIndex],
bottomNavigationBar: BottomNavigationBar(
items: [
BottomNavigationBarItem(
icon: Icon(Icons.add),
title: Text("Red")
),
BottomNavigationBarItem(
icon: Icon(Icons.location_on),
title: Text("Blue")
),
BottomNavigationBarItem(
icon: Icon(Icons.people),
title: Text("Green")
)
],
currentIndex: _bottomIndex,
onTap: _onTabTapped,
)
);
}
_definePages() {
_pages = [
Container(
color: Colors.red,
child: Stack(children: <Widget>[
_defineFloatingActionButton(),
])
),
Container(color: Colors.blue),
Container(color: Colors.green),
];
}
_defineFloatingActionButton() {
return Align(
alignment: Alignment.bottomRight,
child: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
//TODO: How to navigate to another page with still displaying the bottom navigation bar?
}
),
);
}
void _onTabTapped(int index) {
setState(() {
_bottomIndex = index;
_selectedContent = _pages[index];
});
}
}
//POST
class Post extends StatefulWidget {
State createState() => new _PostState();
}
class _PostState extends State<Post> {
#override
Widget build(BuildContext context) {
return Column(children: <Widget>[
PostHeader(),
Text('This is a post.')
]);
}
}
//POSTHEADER
class PostHeader extends StatefulWidget {
State createState() => new _PostHeaderState();
}
class _PostHeaderState extends State<PostHeader> {
#override
Widget build(BuildContext context) {
return ListTile(
leading: Text('Author'),
onTap: () {
//TODO: This should navigate to another page but still displaying the bottom navigation bar, too.
},
);
}
}
But I can't figure out a best practice for more advance navigation. There are 2 problems that I'm currently facing.
When tabbing the FloatingActionButton on the first page, I want to display a fourth page but the BottomNavigationBar still needs to be visible and operable.
Building a more complex app, I'm dealing with a handful of nested classes. So on my root page, there is a class "Post" and the post contains a class "PostHeader". In PostHeader, there is a ListTile with an onTap callback that should affect my _selectedContent. How do I define this callback? Passing it trough all the different classes didn't seem right.
I thought about defining it in my BottomTest.dart and passing it trough Post and PostTile but that doesn't seem like best practice to me, especially when talking about lots of required callbacks.
Thank you very, very much in advance!
I'm assuming that the fourth page will be shown as any of the other three pages and since the button is in the first page, the fourth page will take the place of the first page and still signal the first bottom "red" field as active.
If that is the case you should create an independent widget for the first page that includes all the logic you need to show other content. Thus you avoid rebuilding the main layout, including the BottomNavigationBar.
You could use something along these lines, by using a FirstPage widget:
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 BottomTest(),
);
}
}
class BottomTest extends StatefulWidget {
State createState() => new _BottomTestState();
}
class _BottomTestState extends State<BottomTest> {
List<Widget> _pages;
Widget _selectedContent;
int _bottomIndex;
#override
void initState() {
_bottomIndex = 0;
super.initState();
}
#override
Widget build(BuildContext context) {
_definePages();
return Scaffold(
appBar: AppBar(
title: Text('Bottom Navigation Test'),
),
body: _selectedContent ?? _pages[_bottomIndex],
bottomNavigationBar: BottomNavigationBar(
items: [
BottomNavigationBarItem(icon: Icon(Icons.add), title: Text("Red")),
BottomNavigationBarItem(
icon: Icon(Icons.location_on), title: Text("Blue")),
BottomNavigationBarItem(
icon: Icon(Icons.people), title: Text("Green"))
],
currentIndex: _bottomIndex,
onTap: _onTabTapped,
));
}
_definePages() {
_pages = [
FirstPage(),
Container(color: Colors.blue),
Container(color: Colors.green),
];
}
void _onTabTapped(int index) {
setState(() {
_bottomIndex = index;
_selectedContent = _pages[index];
});
}
}
//POST
class Post extends StatefulWidget {
State createState() => new _PostState();
}
class _PostState extends State<Post> {
#override
Widget build(BuildContext context) {
return Column(children: <Widget>[PostHeader(), Text('This is a post.')]);
}
}
//POSTHEADER
class PostHeader extends StatefulWidget {
State createState() => new _PostHeaderState();
}
class _PostHeaderState extends State<PostHeader> {
#override
Widget build(BuildContext context) {
return ListTile(
leading: Text('Author'),
onTap: () {
//TODO: This should navigate to another page but still displaying the bottom navigation bar, too.
},
);
}
}
class FirstPage extends StatefulWidget {
#override
_FirstPageState createState() => _FirstPageState();
}
class _FirstPageState extends State<FirstPage> {
bool showFirst = true;
_defineFloatingActionButton() {
return Align(
alignment: Alignment.bottomRight,
child: FloatingActionButton(
child: Icon(Icons.add),
onPressed: _onButtonPressed,
),
);
}
_onButtonPressed() {
setState(() {
showFirst = !showFirst;
});
}
_buildFirst() {
return Container(
color: Colors.red,
child: Stack(children: <Widget>[
_defineFloatingActionButton(),
]));
}
_buildFourth() {
return Container(
color: Colors.grey,
child: Stack(children: <Widget>[
_defineFloatingActionButton(),
]));
}
#override
Widget build(BuildContext context) {
return showFirst ? _buildFirst() : _buildFourth();
}
}
For the second point, perhaps you should open another question so you keep two, more or less, unrelated matters in different answers.

How I can view FloatingActionButton on condition

I have list of orders orderList. If that isEmpty, FloatingActionButton is hide. In case orderList have products - FAB will be shown. My code:
bool statusFAB = false;
_getFABState(){
setState(() {
if(!orderList.isEmpty){
statusFAB = true;
}
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: _getFAB(),
backgroundColor: _kAppBackgroundColor,
body: Builder(
builder: _buildBody,
),
);
Widget _getFAB() {
if(statusFAB){
return FloatingActionButton(
backgroundColor: Colors.deepOrange[800],
child: Icon(Icons.add_shopping_cart),
onPressed: null);
}
}
It's not working, because condition work once, but state of orderList can be change anytime.
You don't need to store the statusFAB variable, you can just evaluate it on the fly. See updated sample below:
#override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: _getFAB(),
backgroundColor: _kAppBackgroundColor,
body: Builder(
builder: _buildBody,
),
);
Widget _getFAB() {
if (orderList.isEmpty) {
return Container();
} else {
return FloatingActionButton(
backgroundColor: Colors.deepOrange[800],
child: Icon(Icons.add_shopping_cart),
onPressed: null);
}
}
Well there is a shortcut which can be used with the ternary operator and can be used within Scaffold of a Stateful Widget as
floatingActionButton: orderList.isEmpty ? Container() : FloatingActionButton(...)
Unless you need a long and complicated function, this works fine. Even if you need a complicated function, then that function can be called only when the drawing was needed
floatingActionButton: orderList.isEmpty ? Container() : ComplicatedFn(...)
Widget ComplicatedFn() {
//.... Complicated Algo
return FloatingActionButton(...)
}
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> {
List<Product> orderList = List();
int counter = 0;
void getCount(){
setState(() {
counter = orderList.length;
});
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(widget.title),
),
body: Center(
child: Container(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
IconButton(
onPressed: (){
if(orderList.isNotEmpty)
orderList.removeLast();
getCount();
},
icon: Icon(Icons.remove),
color: Colors.red,
),
Text('$counter'),
IconButton(
onPressed: (){
orderList.add(Product('product'));
getCount();
print('product added');
},
icon: Icon(Icons.add),
color: Colors.blue,
)
],
),
),
),
floatingActionButton: _getFAB()
);
}
Widget _getFAB() {
if (orderList.isEmpty) {
return Container();
} else {
return FloatingActionButton(
backgroundColor: Colors.deepOrange[800],
child: Icon(Icons.shopping_cart),
onPressed: null);
} }
}
class Product {
String title;
Product(this.title);
}