Flutter google mobile ads only shows test ads on IOS - ios

I have the following ad Repository
import 'package:google_mobile_ads/google_mobile_ads.dart';
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:my_app/data/api/constants.dart';
class AdMobRepository {
late String liveBannerAdId;
late String liveInterstitualAdId;
late String liveRewardedAdId;
AdMobRepository() {
if (Platform.isAndroid) {
liveBannerAdId = Constants.androidBannedAdId;
liveInterstitualAdId = Constants.androidInterstitualAdId;
liveRewardedAdId = Constants.androidRewardedAdId;
} else if (Platform.isIOS) {
liveBannerAdId = Constants.iosBannerAdId;
liveInterstitualAdId = Constants.iosInterstitualAdId;
liveRewardedAdId = Constants.iosRewardedAdId;
} else {
liveBannerAdId = "";
liveInterstitualAdId = "";
liveRewardedAdId = "";
}
}
BannerAd getBannerAd({
required AdSize size,
void Function(Ad, LoadAdError)? onFailedLoad,
void Function(Ad)? onLoad,
void Function(Ad)? onAdOpened,
void Function(Ad)? onAdImpression,
}) {
return BannerAd(
adUnitId: kReleaseMode ? liveBannerAdId : BannerAd.testAdUnitId,
request: AdRequest(),
size: size,
listener: BannerAdListener(
onAdFailedToLoad: onFailedLoad ?? onFailedLoadFallback,
onAdLoaded: onLoad,
onAdImpression: onAdImpression,
onAdOpened: onAdOpened,
),
);
}
void onFailedLoadFallback(Ad ad, LoadAdError error) {
ad.dispose();
}
void getInterstitualAd({required void Function(LoadAdError) onFailedLoad, void Function(InterstitialAd)? onLoad}) {
InterstitialAd.load(
adUnitId: kReleaseMode ? liveInterstitualAdId : InterstitialAd.testAdUnitId,
request: AdRequest(),
adLoadCallback: InterstitialAdLoadCallback(
onAdLoaded: onLoad ?? onInterstitialAdLoadedFallback,
onAdFailedToLoad: onFailedLoad,
),
);
}
void onInterstitialAdLoadedFallback(InterstitialAd ad) {
ad.fullScreenContentCallback = FullScreenContentCallback(
onAdDismissedFullScreenContent: (ad) => ad.dispose(), onAdFailedToShowFullScreenContent: (ad, error) => ad.dispose());
}
void getRewardAd({required String userId, required void Function(LoadAdError) onFailedLoad, void Function(RewardedAd)? onLoad}) {
RewardedAd.load(
adUnitId: kReleaseMode ? liveRewardedAdId : RewardedAd.testAdUnitId,
request: AdRequest(),
rewardedAdLoadCallback: RewardedAdLoadCallback(
onAdLoaded: onLoad ?? onRewardedAdLoadedFallback,
onAdFailedToLoad: onFailedLoad,
),
serverSideVerificationOptions: ServerSideVerificationOptions(userId: userId),
);
}
void onRewardedAdLoadedFallback(RewardedAd ad) {
ad.fullScreenContentCallback = FullScreenContentCallback(
onAdDismissedFullScreenContent: (ad) => ad.dispose(), onAdFailedToShowFullScreenContent: (ad, error) => ad.dispose());
}
}
And I have the following widget for banner ads
class MyBannerAd extends StatefulWidget {
const MyBannerAd();
#override
_MyBannerAdState createState() => _MyBannerAdState();
}
class _MyBannerAdState extends State<MyBannerAd> {
late AdSize adSize;
late AdMobRepository adRepository;
late AnalyticsRepository analyticsRepository;
bool adLoaded = false;
BannerAd? anchoredBanner;
#override
void initState() {
super.initState();
adRepository = context.read<AdMobRepository>();
analyticsRepository = context.read<AnalyticsRepository>();
if (SizerUtil.deviceType != DeviceType.mobile && SizerUtil.orientation == Orientation.portrait) {
adSize = AdSize.leaderboard;
} else {
adSize = AdSize.largeBanner;
}
final bannerAd = adRepository.getBannerAd(
size: adSize,
onFailedLoad: (ad, error) {
print('banner ad failed to load: $error');
ad.dispose();
},
onLoad: (ad) {
setState(() {
adLoaded = true;
anchoredBanner = ad as BannerAd?;
});
},
onAdImpression: (_) {
analyticsRepository.sendBannerAdShownEvent();
},
onAdOpened: (_) {
analyticsRepository.sendBannerAdClickEvent();
},
);
bannerAd.load();
}
#override
void dispose() {
super.dispose();
anchoredBanner?.dispose();
}
#override
Widget build(BuildContext context) {
return BlocBuilder<SubscriptionBloc, SubscriptionState>(
builder: (context, state) {
final isLoaded = !adLoaded;
if (isLoaded || state.hasSubscribed || anchoredBanner == null) return SizedBox.shrink();
return Container(
color: Colors.transparent,
width: anchoredBanner!.size.width.toDouble(),
height: anchoredBanner!.size.height.toDouble(),
child: Center(
child: Container(
color: Colors.white,
child: AdWidget(
ad: anchoredBanner!,
),
),
),
);
},
);
}
}
But on IOS it is always showing test ads. How can this be when the app is built with flutter release mode with flutter build ios --release? The app is currently in review and I was thinking that these ads would stop being test ads whenever it is live on the app store.
But Apple sent us the following message
We noticed that your app or its screenshots include test
advertisements. Apps or metadata items that include features that are
for test or demonstration purposes are not appropriate for the App
Store.
Next Steps
To resolve this issue, please revise your app to complete, remove, or
fully configure any partially implemented features. Please ensure your
screenshots do not include any images of demo, test, or other
incomplete content
So how do I get rid of the test ads? Did I miss some XCode setting or?
I am using
flutter: 2.5.3
google_mobile_ads: ^0.13.4
And I also added the GADApplicationIdentifier to my info.plist
<key>GADApplicationIdentifier</key>
<string>{here I have the app Id}</string>
And I am testing on a real device with the testflight build
Side Note:
In admob setting I have added the following test IDFA
00000000-0000-0000-0000-000000000000
which seems to work for Test ads on all IOS devices.

Turns out I needed to remove the 00000000-0000-0000-0000-000000000000 from the test settings on admob. I no longer recieve test ads after that, but I do recieve ads in the release build now.

You don't need to make any code changes.
Next Steps
To resolve this issue, please revise your app to complete, remove, or
fully configure any partially implemented features. Please ensure
your screenshots do not include any images of demo, test, or other
incomplete content
To resolve above rejection, all you need to do is remove banner advertisement from your screenshots and submit for approval again.

Related

Flutter connectivity: Works on Android, but on iOS simulator when I can open webpages in Safari I have internet but the app says there is no internet?

I use this package: https://pub.dev/packages/connectivity_plus
I have a finished application that is working on Android but when I am testing it on iOS it shows that there is no internet. I can use and open pages in Safari so there is definitely one. But the following code returns false in iOS:
class InternetConnectivity with ChangeNotifier {
StreamSubscription<ConnectivityResult>? _subscription;
bool haveInternet = false;
void checkConnectivity() {
if (_subscription == null) {
_subscription = Connectivity().onConnectivityChanged.listen((ConnectivityResult result) {
bool res = result == ConnectivityResult.mobile || result == ConnectivityResult.wifi;
setHaveInternet = res;
});
}
}
set setHaveInternet(bool value) {
if (haveInternet != value) {
haveInternet = value;
notifyListeners();
}
}
}
I don't get any errors so I don't really know where to look for the problem.
On the screen where it checks that internet connection starts with this:
bool _haveInternet = true;
then in initState() I set the value of it:
#override
void initState() {
super.initState();
InternetConnectivity ? _internetConnectivity = InternetConnectivity();
setState(() {
_haveInternet = _internetConnectivity!.haveInternet;
});
After the initState() ran, the _haveInternet becomes false, so the connectivity_plus package returns false while normally it should be true.
Thanks in advance.
The package has a bug. According to documentation it should only affect iOS simulator. https://github.com/fluttercommunity/plus_plugins/issues/479
From package comments:
/// On iOS, the connectivity status might not update when WiFi
/// status changes, this is a known issue that only affects simulators.
/// For details see https://github.com/fluttercommunity/plus_plugins/issues/479.

Flutter image_picker problem on iOS device Flutter

I'm running into a very odd error on M1 Macbook when developing for iOS devices. This is a simple picture picking functionality within the Flutter template (full code here). This works perfectly fine on Android, but not Apple iPhone simulator (running iOS 14 on simulator), where it gives an error that returns null.
floatingActionButton: FloatingActionButton(
onPressed: () async {
await _getImageFromGallery();
//print('Image selected');
},
tooltip: 'Increment',
child: Icon(Icons.add),
),
Below is the image picking function for image_picker: ^0.8.3+1. I tried a few different versions with minimal success.
final picker = ImagePicker();
File? imagePath;
Future _getImageFromGallery() async {
try {
var pickedFile = await picker.pickImage(source: ImageSource.gallery);
print('pickedFile: ${pickedFile!.path}');
setState(() {
imagePath = File(pickedFile.path);
});
} catch (error) {
print('error: $error');
}
}
If anyone could help that would be much appreciated!
Might be a permission problem, you can check out the permission_handler package and see if that fixes your issue.
Add This Permission in Your Android mainfest File
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /><uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
For IOS Add This Line to /ios/Runner/Info.plist:
<key>NSMicrophoneUsageDescription</key>
<string>Microphone Access Required</string>
<key>NSMotionUsageDescription</key>
<string>Motion Usage Required</string>
<key>NSPhotoLibraryAddUsageDescription</key>
<string>Please allow access to photo library so you can easily upload your photos.</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>Please allow access to photo library so you can easily upload your photos.</string>
Add this line in pubspec
image_picker : ^0.8.4
This is code i am currently using for my app.
onTap: () async{
String path = await getImagePath();
print(path);
if(path != "Exceed Limit" && path!="noselect")
{
File imagefile = File(path);
}
}
Future<String> getImagePath() async{
final ImagePicker _picker = ImagePicker();
// Pick an image
final XFile? image = await _picker.pickImage(
source: ImageSource.gallery);
if (image?.path != null) {
print(image!.path);
int size = await image.length();
double sizevalueinmb = size / (1024 * 1024);
if (sizevalueinmb < 5) {
String path = image.path;
return path;
}
else {
print("Your Image File Exceeded Size Limit of 5mb !");
return "Exceed Limit";
}
}
else {
print("User Not Selected Any Image");
return "noselect";
}
}

Is there an INTERNET permission in iOS?

i have a weird problem with dio package for flutter on iOS device.
i wrote an app which sends a GET request to a url. everything works perfectly on Android but looks like the request doesn't go thru on iOS.
nothing happens no error nothing at all. i had the same problem on android too but i found out that i forgot to add INTERNET permission into my manifest file. i'm guessing the same situation occurring in iOS.
is there any INTERNET permission in iOS that i need to add info.plist ?
void _checkVersionAndPreferences() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
String prefsRes = prefs.getString('access_token') ?? '';
String buildNumber = _packageInfo.buildNumber ?? '1';
Dio dio = Dio();
_cancelToken = CancelToken();
Future.delayed(Duration(seconds: 10), () {
if (_getRequestSuccess == false) {
_cancelToken.cancel();
_checkVersionAndPreferences();
_showSnackBar(
content: 'تلاش مجدد برای برقراری ارتباط',
duration: Duration(seconds: 3),
leading: Icon(Icons.refresh, color: Colors.black));
}
});
Response response = await dio.get(
'https://snapmelk.com/api/v1/panel/checkVersion/' + buildNumber,
cancelToken: _cancelToken);
try {
Map respJson = jsonDecode(response.data);
setState(() {
_getRequestSuccess = true;
});
if (respJson['error']) {
_showSnackBar(
content:
(respJson['errorMsg'] != null && respJson['errorMsg'] != '')
? respJson['errorMsg']
: 'خطا در اتصال دریافت اطلاعات آخرین نسخه',
leading: Icon(Icons.warning),
backgroundColor: Colors.red,
textColor: Colors.white);
} else {
if (respJson['NewUpdate']) {
_checkDialogAnswer(respJson, prefsRes);
} else {
_checkPrefs(prefsRes);
}
}
} catch (e) {
_showSnackBar(
content: 'خطا در اتصال با سرور. لطفا در زمانی دیگر مراجعه کنید',
leading: Icon(Icons.warning),
backgroundColor: Colors.red,
textColor: Colors.white);
}
}
There's no network permission that you need to define for iOS in Flutter while using dio. To debug, I suggest logging the response from the executed requested with debugPrint(${response.data});, or if the request using dio itself could possibly throw an error, you might want to consider wrapping it inside the try-catch block as well.

Flutter SharedPreference do not persist

I want to save user preferences using Flutter's SharedPreference. But the registered preferences are ALL null at new start (when app have been closed, not unistalled).
settings.dart :
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
class SettingsPage extends StatefulWidget{
#override
_SettingsPageState createState() => new _SettingsPageState();
}
class _SettingsPageState extends State<SettingsPage>{
bool _param1IsON;
bool _param2IsON;
bool _param3IsON;
#override
void initState() {
super.initState();
_param1IsON = false;
_param2IsON = false;
_param3IsON = false;
loadPreferences();
}
Future<Null> loadPreferences() async {
SharedPreferences _preferences = await SharedPreferences.getInstance();
if(_preferences.getBool('setted') == null || !_preferences.getBool('setted'))
SharedPreferences.setMockInitialValues({}); // Library fix line
bool param1IsON = _preferences.getBool('param1IsON');
bool param2IsON = _preferences.getBool('param2IsON');
bool param3IsON = _preferences.getBool('param3IsON');
setState(() {
_param1IsON = (param1IsON != null)? param1IsON : _param1IsON;
_param2IsON = (param2IsON != null)? param2IsON : _param2IsON;
_param3IsON = (param3IsON != null)? param3IsON : _param3IsON;
});
_preferences.setBool('setted', true);
}
Future<Null> setBoolSettings(String key, bool value) async {
switch(key){
case 'param1IsON':
setState(() {
_param1IsON = value;
});
break;
case 'param2IsON':
setState(() {
_param2IsON = value;
});
break;
case 'param3IsON':
setState(() {
_param3IsON = value;
});
break;
default:
print("Unknown settings '$key'");
}
SharedPreferences _preferences = await SharedPreferences.getInstance();
await _preferences.setBool(key, value);
}
#override
Widget build(BuildContext context) {
return new ListView(
children: <Widget>[
new ListTile(
title: new Text(Param 1'),
trailing: new Switch(value: _param1IsON,
onChanged: (bool newValue) {
setBoolSettings('param1IsON', newValue);
}),
),
new ListTile(
title: new Text('Param 2'),
trailing: new Switch(value: _param2IsON,
onChanged: (bool newValue) {
setBoolSettings('param2IsON', newValue);
}),
),
new ListTile(
title: new Text('Param 3'),
trailing: new Switch(value: _param3IsON,
onChanged:
(bool newValue) {
setBoolSettings('param3IsON', newValue);
}),
),
]
);
}
}
What I get:
At lunch 3 parameters are false. If I turn 'ON' one of them, wait 2s (it is not an async problem), then close the app and Start again... All of my parameters are false.
What I want:
At lunch 3 parameters are false. I turn 'ON' one of them. I close the app. Start again. The previous param I turned 'ON' is still true.
I had the same issue and fixed it in Android/../MainActivity.java by adding at the top:
import io.flutter.plugins.GeneratedPluginRegistrant;
As well as under super.onCreate(savedInstanceState);
GeneratedPluginRegistrant.registerWith(this);
The problem comes from using
SharedPreferences.setMockInitialValues({});
I got it from Flutter Test MissingPluginException but it seems to clear all the shared preferences.
However if you remove SharedPreferences.setMockInitialValues({}); and you don't have the two lines above in MainActivity.java, you'll get:
MissingPluginException(No implementation found for method getAll on
channel flutter: plugins.flutter.io/shared_preferences)
I hope it helps!
Hi I also faced the same issue. Did so many things. nothing helped .This may help someone.First thing ,followed this url and did the changes
1.https://github.com/flutter/flutter/wiki/Upgrading-pre-1.12-Android-projects
2.Run the command
flutter upgrade
3.Changed the shared preferences plugin in pubspec.yaml file
shared_preferences: ">=0.5.10 <2.0.0"
4.Deleted the pub cache from installed flutter location
C:\Users\myuser\AppData\Local\Pub\Cache\hosted\pub.dartlang.org
5.flutter build apk --debug
6.flutter build apk --profile
7.flutter run --release (if I run directly this command its throwing error like debug.jar not found , so I ran command 5 and 6 )
Command 7 is for - To Verify whether its working perfectly in release mode.
Finally I tried to generate app build without shrinking the code. then it worked
flutter build apk --no-shrink
flutter build appbundle --no-shrink

splash screen and one time intro in flutter

I want my splash screen to always appear in my application and it does which is great, but I have a walk through after the splash screen and I want it to be a one time walk through, So i want to add an integer to the shared preferences with a value of 0 and everytime I open the splash screen the value is incremented by one, so when "number" equals 1 or greater at the second run the splash screen skips the walkthrough and goes to home , here is the code that I want to edit now :
void initState() {
// TODO: implement initState
super.initState();
Timer(Duration(seconds: 5), () => MyNavigator.goToIntro(context));
}
And I want it to be like :
void initState() {
// TODO: implement initState
super.initState();int number=0;//this is in the shared prefs
Timer(Duration(seconds: 5), () => if(number==0){MyNavigator.goToIntro(context));
}else{MyNavigator.goToHome(context));
number++;}
}
The below code prints perfectly as we expect(during first launch only, "First launch"). You can use your navigation logic instead of print.
#override
void initState() {
super.initState();
setValue();
}
void setValue() async {
final prefs = await SharedPreferences.getInstance();
int launchCount = prefs.getInt('counter') ?? 0;
prefs.setInt('counter', launchCount + 1);
if (launchCount == 0) {
print("first launch"); //setState to refresh or move to some other page
} else {
print("Not first launch");
}
}
We need to have the number value to be saved across multiple app launches. We can use shared_preference plugin to achieve this.
secondly, getData that saved in our device.
Future<bool> getSaveData() async {
SharedPreferences sharedPreferences = await SharedPreferences.getInstance();
bool isIntroScreenOpenedBefore =
sharedPreferences.getBool("isIntroScreenOpened") ?? false;
print(sharedPreferences.containsKey("isIntroScreenOpened")); // check your key either it is save or not?
if (isIntroScreenOpenedBefore == true) {
Navigator.push(context, MaterialPageRoute(builder: (context) {
return LoginBoard();
}));
} else {
Navigator.push(context, MaterialPageRoute(builder: (context) {
return WalKThroughScreen();
}));
}
return isIntroScreenOpenedBefore;
}
at first, let's save the data as boolean
Future<void> saveData() async {
SharedPreferences sharedPreferences = await SharedPreferences.getInstance();
bool isIntroScreenOpened = true;
sharedPreferences.setBool("isIntroScreenOpened", isIntroScreenOpened); // saved data to your device.
}
Answer by #Dinesh Balasubramanian is works really fine.
But I have 4 initial screen that need to show once. I have done that using same logic in each screen. and then my app was showing 5th screen second time like fast forwarding all the previous screen and stopping on 5th screen.
To resolve this I am getting all the set Preferences at main.dart to open directly 5th screen. but when I do that I am having this problem,
"E/flutter (32606): [ERROR:flutter/lib/ui/ui_dart_state.cc(186)]
Unhandled Exception: Navigator operation requested with a context that
does not include a Navigator.
E/flutter (32606): The context used to
push or pop routes from the Navigator must be that of a widget that is
a descendant of a Navigator widget."
Here is code to switch from main.dart:
int firstLogin, firstMobile, firstOtp, firstInfo;
void setValue() async {
final prefs = await SharedPreferences.getInstance();
firstLogin = prefs.getInt('counterLogin') ?? 0;
firstMobile = prefs.getInt('counterMobile') ?? 0;
firstOtp = prefs.getInt('counterOtp') ?? 0;
firstInfo = prefs.getInt('counterInfo') ?? 0;
prefs.setInt('counterLogin', firstLogin + 1);
prefs.setInt('counterMobile', firstMobile + 1);
prefs.setInt('counterOtp', firstOtp + 1);
prefs.setInt('counterInfo', firstInfo + 1);
if ((firstLogin == 0) && (firstMobile == 0) && (firstOtp == 0) && (firstInfo == 0)) {
setState(() {
print("first launch");
Navigator.of(context).pushNamed(LoginScreen.routeName);
});
} else {
setState(() {
print("not first launch");
Navigator.of(context).pushNamed(LandingSection.routeName);
});
}
}
And calling the setValue() in initState()
I am looking forward for solution.

Resources