I plan to show a banner ad for my app on the top of the screen. When the ad is showing (the user opens the app with an active internet connection) i use Padding Widgets as BottomNavigationItems to place my data below the banner ad. If the user opens the app with no active internet connection (either wifi or mobile data) I get the Ad failed to load : 0 instead of Ad failed to load : 2 which is the official error code for network errors according to https://support.google.com/admob/thread/3494603?hl=en.
Now ive tried to handle if the ad is loaded or not programatically:
void _showBannerAd() async {
_bannerAd = BannerAd(
adUnitId: AdManager.bannerAdUnitId,
size: AdSize.banner,
targetingInfo: _mobileAdTargetingInfo);
bool loaded = await _bannerAd.load();
if (loaded) {
print('success load');
} else {
print('fail load');
}
bool showing =
await _bannerAd.show(anchorOffset: 80.0, anchorType: AnchorType.top);
if (showing) {
print('sucess show');
} else {
print('fail show');
}
}
which i load
#override
void initState() {
_showBannerAd();
super.initState();
}
But even when there is no internet connection the bool values are false although they shouldnt?
Any help please! I want the user to have a good experience - even with 1 Ad in my entire app it looks weird if the ad is not showing and all the data is still padded.
Thanks guys!
Ive finally found an answer to my own question after searching for a while.
Turns out every Admob ad has a listener attribute!
So what i did is
I created a static bool variable in my BottomNavPageState (BottomNavPage is a Statefulwidget) assuming that the user somehow has an internet connection at startup:
static bool adError = false;
Also every BottomNavItemPage is a StatefulWidget. When initialising my routes i just pass the adError variable (at startup) into all the routes which use Ads for now:
List<Widget> _routes = [
TodayPage(),
PlanPage(adError: adError),
TodoPage(adError: adError),
ProgressPage()
];
When intialising my Banner ad i add the Listener to the banner:
void _showBannerAd() async {
_bannerAd = BannerAd(
adUnitId: AdManager.bannerAdUnitId,
size: AdSize.banner,
targetingInfo: _mobileAdTargetingInfo,
listener: (event) {
if (event == MobileAdEvent.failedToLoad) {
setState(() {
adError = true;
_routes = [
TodayPage(),
PlanPage(adError: adError),
TodoPage(adError: adError),
ProgressPage()
];
});
}
});
}
If the ad failes to load i set the State of my BottomNavPageState in which i turn the bool variable to true and reinitialise my routes with the new updated bool.
Also dont forget to add the adError variable to the BottomNavItemPage:
class TodoPage extends StatefulWidget {
final bool adError;
TodoPage({this.adError});
#override
_TodoPageState createState() => _TodoPageState();
}
...
and access the variable to make simple if else check to return the given Padding:
...
Padding(
padding: widget.adError
? EdgeInsets.only(left: 8.0, right: 8.0)
: EdgeInsets.only(top: 60.0, left: 8.0, right: 8.0),
child:
...
And i have to say it works like a charm!
Related
I'm trying to implement an Adaptive Banner in Flutter but the banner size doesn't get updated when a phone is rotated from landscape to portrait and vs visa. I don't know if this is a bug or something that I did wrong.
This is what it looks like in landscape mode.
This is what it looks like in portrait mode.
I'm using the latest google_mobile_ads
This is Ad load function
Future<void> _loadAd() async {
final AnchoredAdaptiveBannerAdSize? size = await AdSize.getCurrentOrientationAnchoredAdaptiveBannerAdSize(
MediaQuery.of(context).size.width.truncate());
if (size == null) {
print('Unable to get height of anchored banner.');
return;
}
_anchoredAdaptiveAd = BannerAd(
adUnitId: AdHelper.bannerAdUnitId,
size: size,
request: AdRequest(),
listener: BannerAdListener(
onAdLoaded: (Ad ad) {
// print('$ad loaded: ${ad.responseInfo}');
_isBannerAdReady = true;
setState(() {
_anchoredAdaptiveAd = ad as BannerAd;
_isBannerAdReady = true;
});
},
onAdFailedToLoad: (Ad ad, LoadAdError error) {
print('Anchored adaptive banner failedToLoad: $error');
ad.dispose();
},
),
);
return _anchoredAdaptiveAd!.load();
}
I tried to reload in the build function but it doesn't re-calculate the size. Any work around to this problem?
Widget build(BuildContext context) {
orientation = MediaQuery.of(context).orientation;
if (porientation == null) {
porientation = orientation;
} else {
if (porientation != orientation) {
//reload ad when orientation is changed
reloadAd();
porientation = orientation;
}
}
This is normal. The place holder ads are not dynamic and mostly the ads are cut off. If you notice the test tag is in the center. Be rest assured that the real ads will be placed properly
I'm not using API or firebase yet, the data is stored locally.
here is my code
======>>
Widget _buildhouse(BuildContext contex, int index){
Size size = MediaQuery.of(context).size;
House house = houselist[index]; //houselist is the list of all houses
return GestureDetector(
onTap: (){
setState(() {]
house = filteredhouse[index]; //this code wont be executed
print(house.price);
});
Navigator.push(context, MaterialPageRoute(builder: (_) => DetailsScreen(house),));
},
so those two lines that I commented on are the important ones I guess, the print code gets executed but not the other one. also if I say "house = filteredhouse[index];" at the beginning, I will get the filtered value. but it won't get changed when clicked the button
solved it by using this function void _filtered(){ setState(() { houselist = filteredhouse; }); } and then calling it inside of setState
I am building a comics app. In order to make comic chapters load faster. I want images to load one by one consequtively. When an image finishes loading, it is presented directly into a pageview. I do not know how to load images one by one and edit the page view to present them.
In order to make load image faster you can try use cached_network_image package like this:
CachedNetworkImage(
imageUrl: "http://via.placeholder.com/350x150",
placeholder: (context, url) => CircularProgressIndicator(),
errorWidget: (context, url, error) => Icon(Icons.error),
),
It will show placeholder until the image loaded, so the user can see how many image there is and wait until it loaded, and errorWidget to show if some image were getting trouble. So I suggest to use it, it's easier. Unless you want some complicated code you can try use ScrollController put it inside your ListView.builder. Initialize your scroll controller in initState like this :
final _scrollController = ScrollController();
#override
void initState() {
super.initState();
_scrollController.addListener(() {
if (_scrollController.position.pixels ==
_scrollController.position.maxScrollExtent) {
// scroll has reach end, now load more images.
loadMore();
}
});
}
You can't use setState inside initState so that's why I create another method to call it like this:
Future<void> loadMore() async {
final response = await api.get("curated?per_page=50&page=$_currentPage");
if (response.statusCode == 200) {
var tempList = Pages.fromJson(response.data);
setState(() {
isLoading = false;
wallpaper.addAll(tempList.photos);
});
}
return null;
}
So there you go I hope you can understand, and yes it is more complicated, so like I said I suggest you use CachedNetworkImage.
use a StreamBuilder it will do all the work for u.
I am trying to achieve a very basic thing: Display Admob Banner ad on top. This works, but the Banner Ad slips inside the status bar and I couldn't find any way to make it display properly. Here is the sample code I am trying:
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
// This is the theme of your application.
//
// Try running your application with "flutter run". You'll see the
// application has a blue toolbar. Then, without quitting the app, try
// changing the primarySwatch below to Colors.green and then invoke
// "hot reload" (press "r" in the console where you ran "flutter run",
// or simply save your changes to "hot reload" in a Flutter IDE).
// Notice that the counter didn't reset back to zero; the application
// is not restarted.
primarySwatch: Colors.blue,
// This makes the visual density adapt to the platform that you run
// the app on. For desktop platforms, the controls will be smaller and
// closer together (more dense) than on mobile platforms.
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: HomeView(),
);
}
}
class HomeView extends StatefulWidget {
#override
_HomeViewState createState() => _HomeViewState();
}
Future<void> _initAdMob() {
return FirebaseAdMob.instance.initialize(appId: AdManager.appId);
}
class _HomeViewState extends State<HomeView> {
// COMPLETE: Add _bannerAd
BannerAd _bannerAd;
// COMPLETE: Implement _loadBannerAd()
void _loadBannerAd() {
_bannerAd
..load()
..show(anchorType: AnchorType.top);
}
#override
void initState() {
_bannerAd = BannerAd(
adUnitId: AdManager.bannerAdUnitId,
size: AdSize.banner,
);
super.initState();
}
#override
Widget build(BuildContext context) {
return FutureBuilder<void>(
future: _initAdMob(),
builder: (BuildContext context, AsyncSnapshot<void> snapshot) {
_loadBannerAd();
return SafeArea(
child: new Column(children: <Widget>[
Text('Sample text')
],),
);
},
);
}
}
This code produces the below output:
which obviously is wrong. The text is right at the place where it should be, however, the Banner ad is slipped inside the status bar, which is not what I intend. Also, since Google doesn't support providing positional arguments for the banner ad, I am completely helpless.
However, the Banner ad in the Google Codelab for Flutter works very well which is way beyond my understanding as a Flutter novice.
Can someone please shed a light and guide me on what's wrong with the sample code?
I'm building an app that basically is a YouTube clone. I use the official video_player plugin for playback and chewie for controls. I'd like to implement a quality switcher, so the user can decide what quality they want the video to be streamed at
I've built a bottom sheet with switches and I run changeQuality() when the user selects the desired quality. What it should do is simply giving a new source file to the old player and keep playing from where the video left.
This is the video player and chewie player that run on initState():
videoPlayer = VideoPlayerController.network(data == null
? dataAll[indexNo]["video"]["480"]
: data[indexNo]["video"]["480"]);
chewieController = ChewieController(
videoPlayerController: videoPlayer,
aspectRatio: 16 / 9,
autoPlay: true,
allowedScreenSleep: false,
placeholder: data == null
? Image(
image: NetworkImage(dataAll[indexNo]["thumbnail"]),
)
: Image(
image: NetworkImage(data[indexNo]["thumbnail"]),
)
);
And the changeQuality() function:
changeQuality(String newQuality) {
setState(() {
position = videoPlayer.value.position;
chewieController.pause();
videoPlayer = new VideoPlayerController.network(data == null
? dataAll[indexNo]["video"]["$newQuality"]
: data[indexNo]["video"]["$newQuality"]);
chewieController = ChewieController(
videoPlayerController: videoPlayer,
aspectRatio: 16 / 9,
autoPlay: true,
allowedScreenSleep: false,
startAt: position,
);
});
Navigator.of(context).pop();
}
I've also tried disposing the old video player and then setting the new value, but I get an error that variables cannot be used after being disposed.
The switcher works a bit, because it changes the quality around 4 to 5 times and then it runs into an error and won't play anything.
I expand upon this solution for video_player and extend it to also cover chewie.
Key parts of this solution
You need two widgets. MyVideoPlayer that encapsulates video_player and chewie and an outer widget where you react to user input or state changes and swap out MyVideoPlayer with a new one.
This solution roundabouts the whole question in one way. I doesn't solve how to change video of video_player or chewie. Instead it follows the documented principal on how to use chewie for the whole life cycle of a host widget (MyVideoPlayer) and swap that one out to change video url.
You can stuff in more things in the outer widget as you see fit if you don't want to dedicate it just to containing MyVideoPlayer. Ie. if you want a description text adjacent to it based on app state.
Outer Widget
I write with this. but it can be omitted in Dart code.
class QuizVideoPlayer extends StatefulWidget {
#override
_QuizVideoPlayerState createState() => _QuizVideoPlayerState();
}
class _QuizVideoPlayerState extends State<QuizVideoPlayer> {
Word _url;
UniqueKey _urlKey;
// Call this method from button or in reaction to model change etc.
// I call it from Provider.of in didChangeDependencies, but I don't think it is
// a necessary detail of the answer as it depends on how you do state management.
// The key in this solution is that state management occur in the outer widget and
// due to some trigger call _changeUrl() which changes _url and _urlKey which then
// swaps out MyVideoPlayer.
#override
void _changeUrl(String newUrl) async {
this.setState(() {
// Rebuild MyVideoPlayer with a new instance => eventually dispose old controllers
this._url = newUrl;
this._urlKey = UniqueKey();
});
}
#override
Widget build(BuildContext context) {
return
/* ... */
this._url != null
? MyVideoPlayer(
this._url,
this._urlKey,
)
: AspectRatio(
aspectRatio: 3 / 2,
child: Container(color: Colors.black),
)
/* ... */
);
}
}
MyVideoPlayer
I write with this. but it can be omitted in Dart code.
import 'package:flutter/material.dart';
import 'package:video_player/video_player.dart';
import 'package:chewie/chewie.dart';
class MyVideoPlayer extends StatefulWidget {
final String videoUrl;
final UniqueKey newKey;
MyVideoPlayer(this.videoUrl, this.newKey): super(key: newKey); // passing Unique key to dispose old class instance and create new with new data
#override
_MyVideoPlayerState createState() => _MyVideoPlayerState();
}
class _MyVideoPlayerState extends State<MyVideoPlayer> {
VideoPlayerController _controller;
ChewieController _chewie;
#override
void initState() {
this._initControllers(this.widget.videoUrl);
super.initState();
}
void _initControllers(String url) {
this._controller = VideoPlayerController.network(url);
this._chewie = ChewieController(
videoPlayerController: this._controller,
autoPlay: true,
);
}
#override
void dispose() {
this._controller?.dispose();
this._chewie?.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Chewie(controller: this._chewie);
}
}