ok I am working on concept idea my dad has pitched to me. I have an app that runs AdMobs. On the interstitial ads based off button. The idea of the app is you press the start button and you watch an ad. However, when the ad is closed out, the value should increase in the Ads Watched Field.
I have created a function that increases the TextView no problem. My issue is with AdMob functions, when I call the function in AdDismissed, it does not change the value. I can plug the function into the Start Button and it increases value, but when the Ad is dismissed it zeros out the textView.
I am showing the demo portion of the app, this is still experimental, but also learning with Admobs and the coding on functions. Any advice would be appreciated. Also the adCounter is in the stop button, that was just to make sure the increments where firing. Which it does work perfectly. My thing is when the ad ends keeping the value.
SO in example the Ads Watched: 167,897,256 should increment by one when the ad is dismissed. However placing adCount() in the dismissed section of the ad does not work it just zeros out that textView.
MainActivity
import android.content.Intent
import android.os.Bundle
import android.widget.Button
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import com.google.android.gms.ads.*
import com.google.android.gms.ads.interstitial.InterstitialAd
import com.google.android.gms.ads.interstitial.InterstitialAdLoadCallback
class MainActivity : AppCompatActivity() {
lateinit var mAdView : AdView
private var mInterstitialAd: InterstitialAd? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
loadBanner()
loadInterAd()
val interAdBtnStart : Button = findViewById(R.id.btnStartAds)
val interAdBtnStop : Button = findViewById(R.id.btnStopAds)
interAdBtnStart.setOnClickListener {
showInterAd()
}
interAdBtnStop.setOnClickListener {
adCountInc()
}
}
fun adCountInc(){
val tvAdsAmount : TextView = findViewById(R.id.tvAdsAmount)
var i : Int = tvAdsAmount.text.toString().toInt()
tvAdsAmount.text = "${++i}"
}
private fun showInterAd() {
if (mInterstitialAd != null)
{
mInterstitialAd?.fullScreenContentCallback = object : FullScreenContentCallback(){
override fun onAdClicked() {
super.onAdClicked()
}
override fun onAdDismissedFullScreenContent() {
super.onAdDismissedFullScreenContent()
val intent = Intent(this#MainActivity, MainActivity::class.java)
startActivity(intent)
}
override fun onAdFailedToShowFullScreenContent(p0: AdError) {
super.onAdFailedToShowFullScreenContent(p0)
}
override fun onAdImpression() {
super.onAdImpression()
}
override fun onAdShowedFullScreenContent() {
super.onAdShowedFullScreenContent()
}
}
mInterstitialAd?.show(this)
}
else
{
val intent = Intent(this, MainActivity::class.java)
startActivity(intent)
}
}
private fun loadInterAd() {
var adRequest = AdRequest.Builder().build()
InterstitialAd.load(this,"ca-app-pub-3940256099942544/1033173712", adRequest, object : InterstitialAdLoadCallback() {
override fun onAdFailedToLoad(adError: LoadAdError) {
mInterstitialAd = null
}
override fun onAdLoaded(interstitialAd: InterstitialAd) {
mInterstitialAd = interstitialAd
}
})
}
private fun loadBanner() {
MobileAds.initialize(this) {}
mAdView = findViewById(R.id.adView)
val adRequest = AdRequest.Builder().build()
mAdView.loadAd(adRequest)
mAdView.adListener = object: AdListener() {
override fun onAdLoaded() {
// Code to be executed when an ad finishes loading.
}
override fun onAdFailedToLoad(adError : LoadAdError) {
// Code to be executed when an ad request fails.
}
override fun onAdOpened() {
// Code to be executed when an ad opens an overlay that
// covers the screen.
}
override fun onAdClicked() {
// Code to be executed when the user clicks on an ad.
}
override fun onAdClosed() {
// Code to be executed when the user is about to return
// to the app after tapping on an ad.
}
}
}
}
this is the full code to the app so far. Any advice will help. If i place the adCounter() anywhere in the ads section it will not update the textfield at all. Even after the textfield shows 1 then an ad is displayed it will always zero out the text field.
The value is not updated because you are opening the same Activity (MainActivity) on onAdDismissedFullScreenContent again.
First create a global TextView variable like:
private lateinit var tvAdsAmount : TextView`\
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
tvAdsAmount = findViewById(R.id.tvAdsAmount)
// Other things...
}
Then simply use:
override fun onAdDismissedFullScreenContent() {
val value = tvAdsAmount.text.toString().toInt()
// make sure that value is an Integer.
val updateValue = value++
tvAdsAmount.text = "$updatedValue"
}
Some points you should learn:
Every time you start a new Activity, you're getting a new TextView that has no memory of what was in a TextView of some previous Activity.
You should never use a UI component like TextView to store application state. It's just not reliable. UI components are intended to be a bridge between your application state and the user. They aren't supposed to be the application state themselves. This is the programming principle of separation of concerns.
Since you're not finishing the previous Activity, you're building up a large stack of duplicate Activities. The user will be surprised when they push the back button to see an outdated copy of the Activity, one after another.
Whenever there's a configuration change (such as a screen rotation, or the user changing some setting in the Android settings like the device language), Android destroys all of the Activities that you have open and creates new instances of them. So any application state you were holding in them is going to be lost. This is why there is a ViewModel class for holding state that will survive configuration changes.
To fix your app:
Change your logic so you aren't starting new Activities. Keep everything in the same Activity instance. If you want to load a new ad, just call the function that loads ads rather than creating a brand new Activity.
Create a ViewModel to hold your application state. In this case, it will just need a LiveData<Int> to hold your count. You can observe this LiveData in your Activity and update the value of the TextView in the observer function. Your ViewModel can have an increment function that increases the LiveData's integer value, and you'll call this after ads are dismissed.
Long term, you can consider backing up this value with SharedPreferences, so the value will persist between sessions of your app.
Points 2 and 3 have many tutorials online and questions on this site about them, so I'm not going to explain them in detail.
Related
So, I would like to use StateFlow instead of LiveData, but I can not figure out what's the problem with my logic.
I have a flow, which has a default null value. When I open a dialog which contains a some datas, after that I select one data, I emit the new value to the flow.
In the first time, after the dialog closed, collectLatest called, and I get the null value (init), after the emit, I get the new value, it is good. But If I open the dialog again, and select value, and close the dialog, the collectLatest fun called 3-times, and I again open the dialog... and collectLatest called 4 times and so on.
So this is very bad behavior, and I'm sure , I did something wrong, but I don't see the bug.
In the liveData the expected behavior is after the dialog close, that the observer fun is called just once. I would like to achive this.
I also checked, that I emit the new value only once, so there is no reason why collectLatest fire multiple times.
ViewModel:
private val _previousManufacture = MutableStateFlow<PreviousManufactureView?>(null)
val previousManufacture = _previousManufacture.asStateFlow()
private suspend fun setPreviousManufactureByMachineId(machineId: String) {
val result = stateReportRepository.getPreviousManufactureByMachineId(machineId)
if (result is Result.Success) {
_previousManufacture.emit(result.data)
} else {
_previousManufacture.emit(null)
}
}
Fragment:
lifecycleScope.launchWhenCreated {
viewModel.previousManufacture.collectLatest {
var d = it
}
}
[Update]
Fragment:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.vm = viewModel
initFlows()
}
private fun initFlows() {
lifecycleScope.launchWhenCreated {
viewModel.openStateOfWorkflowBrowser.collectLatest {
openStateOfWorkflowSelectionDialog()
}
}
...
}
Sorry, I missed this before in my comment, but I think the problem is that you are calling launchWhenCreated in the lifecycleScope of the Fragment, not in its viewLifecycle.lifecycleScope. So if the Fragment is reused (like after a dialog fragment has a appeared), the old collector is not cancelled and a new one is added, because the lifecycle of the Fragment has not ended, only the lifecycle of its previous view. You should almost always use viewLifecycle.lifecycleScope when you are using coroutines in a Fragment.
I am trying to show an interstitial ad by calling the bellow function each time a button is clicked from a composable. It works alright for the first click but doesn't get loaded for the next clicks. What am I missing here?
fun loadInterstitial(context: Context) {
InterstitialAd.load(
context,
context.getString(R.string.ad_id_interstitial),
AdRequest.Builder().build(),
object : InterstitialAdLoadCallback() {
override fun onAdFailedToLoad(adError: LoadAdError) {
mInterstitialAd = null
Log.d("MainActivity", adError.message)
}
override fun onAdLoaded(interstitialAd: InterstitialAd) {
mInterstitialAd = interstitialAd
Log.d("MainActivity", "Ad was loaded.")
}
}
)
}
Here is the rest of the interstitial code if necessary.
Thanks for your help!
Ok from the link pasted in the comment, I got this code:-
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
AdNetworkTheme {
Surface(color = MaterialTheme.colors.background) {
AdNetworkApp()
}
}
}
// initialize the Mobile Ads SDK
MobileAds.initialize(this) { }
// load the interstitial ad
loadInterstitial(this)
// add the interstitial ad callbacks
addInterstitialCallbacks(this)
}
}
Clearly, the method being called in the setContent block is the problem.
The setContent block is executed only once until some factor like screen rotation or something to that effect triggers a hundred point recomposition. Now, you are calling the loading method in the setContent. Hence, the ad is loaded for the first time (so you see the logs for the first time). Afterwards, you do not call it anywhere at all. No ads loaded - nothing to show.
You can check this by rotating your screen. The ad will be loaded once more then.
Ok, solution could be to just go ahead and place the loading call in the onClick of the Button itself. I don't think anything else would be necessary.
I'm using retrofit2 and coroutines, I have a fragment A and B. I made the callback to fill a recyclerView at fragment B but if the user goes back to fragment A before the callback is finished, the app crashes because there is no B fragment to receive the data.
Of course, I can disable the back button until I receive the data from the callback, but I'm sure there is a better solution.
I found a navController class removeOnDestinationChangedListener, but I'm not sure how to use it or if it may help me with this problem.
Any Ideas?
main declarations inside the Fragment:
private lateinit var binding: FragmentShowCustomerBinding
var process: Job? = null
var productHistory: ArrayList<ProductHistory> = arrayListOf()
val producthistoryAdapter: ProductHistoryAdapter = ProductHistoryAdapter(arrayListOf())
This is how I call the service:
private fun loadCustomerActivity() {
process =
CoroutineScope(Dispatchers.IO).launch {
MainService.getCustomerOrders(customer.documentNumber, 1)
{ customerActivity ->
productHistory = customerActivity
CoroutineScope(Dispatchers.Main).launch {
binding.progressBarCustomerPurchaseHistory.visibility = View.INVISIBLE
binding.recyclerViewProductHistory.let {
producthistoryAdapter.loadCustomerhistory(productHistory)
producthistoryAdapter.notifyDataSetChanged()
}
}
}
}
}
and this how I cancel it just in case the user get out from the fragment before I get the customer history:
override fun onStop() {
super.onStop()
process?.cancel()
}
Let me describe the application im trying to do using Kotlin, on Android Studio.
Splash Screen => Login Screen => Main App
Splash Screen: Just a photo
Login Screen: Provides different ways of logging (Google, Facebook, etc)
MainActivity: Allows you to log off, in that case, you must return to "Login Screen"
So far I have been working only with Facebook Login.
I managed to place the button, make it work, and get a proper uid. The trouble is that, when that button is clicked, it automatically switches to "Log Out". So, when I go back from my MainActivity to my Login Screen, instead of having the button to Login again, im having a "Log Out" button, when account is actually already logged out.
Is there a way to prevent this button from changing? I have been reading tons of documentation, but havent found anything useful.
Is my idea incorrect? Or is there a better way to do this?
Note that everytime I Leave the LoginScreen, I place a finish(). The reason of this was to try to reset the activity, and make it work as if the program was running from scratch.
Variables defined on LoginScreen
private var mCallBackManager : CallbackManager?= null
private var mFirebaseAuth : FirebaseAuth?= null
On create function
override fun onCreate(savedInstanceState: Bundle?)
{
super.onCreate(savedInstanceState)
setContentView(R.layout.lay_login_screen)
//-----------Inicializadores-Facebook---------------
mFirebaseAuth = FirebaseAuth.getInstance()
FacebookSdk.sdkInitialize(getApplicationContext())
mCallBackManager = CallbackManager.Factory.create()
//--------------------------------------------------
//-----------Boton-Facebook-------------------------
Button_LoginScreen_LoginFace.setOnClickListener()
{
iniciarSesionFacebook();
}
//--------------------------------------------------
}
private fun iniciarSesionFacebook()
{
Button_LoginScreen_LoginFace.registerCallback(mCallBackManager, object : FacebookCallback<LoginResult>
{
override fun onSuccess(result: LoginResult?)
{
d(getString(R.string.TAG_FacebookLogin),"Login Correct")
handleFacebookToken(result!!.accessToken)
}
override fun onCancel()
{
d(getString(R.string.TAG_FacebookLogin),"Login cancelled")
}
override fun onError(error: FacebookException?)
{
d(getString(R.string.TAG_FacebookLogin),"Login Error")
}
})
}
private fun handleFacebookToken(accessToken: AccessToken?)
{
val credential = FacebookAuthProvider.getCredential(accessToken!!.token)
mFirebaseAuth!!.signInWithCredential(credential).addOnFailureListener()
{error->
d(getString(R.string.TAG_FacebookLogin),"Error 1"+error.message)
}
.addOnSuccessListener { resultado->
startActivity(Intent(this, MainActivity::class.java))
finish()
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?)
{
super.onActivityResult(requestCode, resultCode, data)
mCallBackManager!!.onActivityResult(requestCode,resultCode,data)
}
Finally, this is how I "Log Out" from MainActivity, and return to LoginScreen
class MainActivity : AppCompatActivity()
{
//----------Variables-Globales----------------
private var mFirebaseAuth : FirebaseAuth?= null
//--------------------------------------------
override fun onCreate(savedInstanceState: Bundle?)
{
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
//-------Inicializo-Variables------------------------
mFirebaseAuth = FirebaseAuth.getInstance()
//---------------------------------------------------
Button_MainActity_LogOf.setOnClickListener{
val firebaseaux=mFirebaseAuth
firebaseaux?.signOut()
startActivity(Intent(this, ActivityLoginScreen::class.java))
}
}
}
This is how the LoginScreen looks the first time is ran.
This is how it looks after manually logging of. Note that is not only the appearence of the code, the code itself changes. Now the button doesnt allow you to LogIn.
Just to add some extra information, i have found this property on the docs
Configuration: auto_logout_link
HTML5 Attribute: data-auto-logout-link
Description: If its activated, the button will be replaced by a LogOut button if the user has already logged in
Options: True,False
This es EXACTLY what im looking for. But, from what I can see, that is only ment for webpages and not android. Does somebody know how to touch this configuration in android? or the equivalent adroid option?
I will add the link where I found the property.
https://developers.facebook.com/docs/facebook-login/web/login-button/
Thanks in advance
Ok, after some time I managed to solve the Issue.
I was logging out from the Firebase Authentication, but not from Facebook authentication.
I didnt manage to stop the button from behaving like it does, but at least the behaviour now is correct.
I will share my new LogOut code.
Button_MainActity_LogOf.setOnClickListener{
FirebaseAuth.getInstance().signOut() //Log out from Firebase
if(isFacebookLogin()) //Check if logged in on facebook
{
LoginManager.getInstance().logOut() //Log out from facebook
}
//Here i should log out from other providers
startActivity(Intent(this, ActivityLoginScreen::class.java))
}
private fun isFacebookLogin(): Boolean
{
return AccessToken.getCurrentAccessToken() !=null
}
My problem is:
I close Activity using finish(), it goes to onPause -> onStop -> onDestroy.
Next I open app, onCreate() gets old references to all views and context.
When I try show simple dialog it throws:
"Unable to add window -- token android.os.BinderProxy#69a156a is not
valid; is your activity running?"
I also cannot access text view
progressText?.text = message
it gets old reference - i used clearFindViewByIdCache() -- but no effect.
What's wrong?
EDIT
I try to manipulate views from DataSyncListener methods runOnUiThread
class MainActivity : AppCompatActivity(), DataSyncListener {
override fun onSuccess() {
runOnUiThread {
refreshLayout?.isRefreshing = false // it DO NOT works after reopen app,
syncProgressText?.visibility = View.GONE // it DO NOT works after reopen app,
}
}
override fun onFailure() {
runOnUiThread {
refreshLayout?.isRefreshing = false // it DO NOT works after reopen app,
syncProgressText?.visibility = View.GONE // it DO NOT works after reopen app,
}
}
override fun onError(message: String) {
Logger.d(message)
runOnUiThread {
refreshLayout?.isRefreshing = false // it DO NOT works after reopen app
syncProgressText?.visibility = View.GONE // it DO NOT works after reopen app
displayInfoAlertWithConfirm(this#MainActivity, message, DialogInterface.OnClickListener { _, _ -> // it DO NOT works after reopen app, throws Unable to add window
refreshLayout?.isRefreshing = true // it DO NOT works after reopen app
syncProgressText?.visibility = View.VISIBLE // it DO NOT works after reopen app
})
}
}
override fun onProgress(message: String) {
runOnUiThread {
syncProgressText?.text = message // it DO NOT works after reopen app
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setSupportActionBar(toolbar)
refreshLayout.setOnRefreshListener({
// it DO NOT works after reopen app,
synchronizeData()
})
synchronizeData()
syncProgressText?.text = "test" // it works after reopen app
}
override fun onPostCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) {
super.onPostCreate(savedInstanceState, persistentState)
actionBarDrawerToggle?.syncState()
}
fun synchronizeData() {
refreshLayout?.isRefreshing = true
dataSynchronizer = DataSynchronizer.getInstance(application as MyApplication?, this)
dataSynchronizer?.startSync() // background featch data
syncProgressText?.visibility = View.VISIBLE // it DO NOT works after reopen app
}
override fun onDestroy() {
super.onDestroy()
dataSynchronizer?.stopSync() // kill background task
clearFindViewByIdCache() // no effect
}
}
EDIT2
FIXED - DataSynchronizer was not GC and hold old references
Use syncProgressText.setText(message), syncProgressText.text expects Editable, not a String.
Finally fixed. Thanks #Viktor, after checked my DataSynchronizer.getInstance(application as MyApplication?, this) I realized that DataSynchronizer was not GC -- memory leaks. So it holded old refrerences. Now it works like a charm.