I faced a strange bug with admob native ads. Everything works fine except that I cannot find url value for an ads on callback and that UnifiedNativeAdView is not clickable, even button inside don't go anywhere as I use admob templates.
Here is the XML view code:
<com.google.android.gms.ads.formats.UnifiedNativeAdView
android:paddingTop="8dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/native_ad_view"
android:background="#000000"
android:elevation="20dp">
<LinearLayout
android:id="#+id/native_ad_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:orientation="vertical">
<TextView
style="#style/AdAttribution"
android:id="#+id/native_ad_attribution"
android:visibility="gone"/>
<LinearLayout
android:id="#+id/native_ad_inside_layout"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_gravity="center"
android:layout_weight="1"
android:orientation="horizontal"
android:visibility="gone">
<ImageView
android:id="#+id/ad_app_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:adjustViewBounds="true"
android:maxWidth="85dp"
android:scaleType="centerCrop" />
<TextView
android:id="#+id/ad_headline"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_weight="1"
android:maxLines="3"
android:paddingLeft="5dp"
android:paddingRight="5dp"
android:textSize="16sp"
android:textStyle="bold" />
<Button
android:id="#+id/ad_call_to_action"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"
android:maxWidth="50dp"
android:maxLines="3"
android:textSize="12sp"
/>
</LinearLayout>
</LinearLayout>
</com.google.android.gms.ads.formats.UnifiedNativeAdView>
Code of Viewholder:
inner class TripViewHolder(val view: View) : RecyclerView.ViewHolder(view) {
val adView: UnifiedNativeAdView = view.native_ad_view as UnifiedNativeAdView
var layoutView: LinearLayout = view.native_ad_layout
var insideLayoutView: LinearLayout = view.native_ad_inside_layout
var attributionView: TextView = view.native_ad_attribution
var headlineView: TextView = view.ad_headline
var callToActionView: Button = view.ad_call_to_action
var iconView: ImageView = view.ad_app_icon
}
Code of onBindViewHolder:
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val item = mValues[position]
if (item.nativeAd == null) {
return
}
if (item.nativeAd!!.headline == null) {
return
}
holder.attributionView.setVisibility(View.VISIBLE);
holder.insideLayoutView.setVisibility(View.VISIBLE);
holder.headlineView.text = item.nativeAd!!.headline
if (item.nativeAd!!.callToAction == null) {
holder.callToActionView.visibility = View.INVISIBLE
} else {
holder.callToActionView.visibility = View.VISIBLE
(holder.callToActionView as Button).text = item.nativeAd!!.callToAction
}
if (item.nativeAd!!.icon == null) {
holder.iconView.visibility = View.GONE
} else {
(holder.iconView as ImageView).setImageDrawable(item.nativeAd!!.icon.drawable)
holder.iconView.visibility = View.VISIBLE
}
// Assign native ad object to the native view.
holder.adView.setNativeAd(item.nativeAd!!)
}
And this is where I call adLoader. I call it in the background using BroadcastReceiver:
fun loadNativeAds(context: Context, listener: MyTripsContent.ContentEventsListener?) {
if (!adList.isEmpty()) {
return
}
try {
var unitId: String? = ""
lateinit var adLoader: AdLoader
//test key
unitId = "ca-app-pub-3940256099942544/2247696110"
adLoader = AdLoader.Builder(context, unitId)
.forUnifiedNativeAd { ad: UnifiedNativeAd ->
// Show the ad.
adList.add(ad)
if (adList.size == 5) {
listener?.onLoadedDocuments(0)
}
}
.withAdListener(object : AdListener() {
override fun onAdFailedToLoad(errorCode: Int) {
// Handle the failure by logging, altering the UI, and so on.
if (!adLoader.isLoading) {
}
}
override fun onAdClicked() {
super.onAdClicked()
}
})
.withNativeAdOptions(
NativeAdOptions.Builder()
// Methods in the NativeAdOptions.Builder class can be
// used here to specify individual options settings.
.build()
)
.build()
adLoader.loadAds(AdRequest.Builder().build(), 5)
} catch (exception: Exception) {
exception.printStackTrace()
}
}
So as I mentioned ads work well but they are not clickable. I'm not sure if this is a bug from admob, or my code or something I am missing. I think other developers will face the same issue in the future, so it would be good to solve it.
Thank you in advance.
So due to lack of detailed documentation and examples I found I was missing one line of code. Many other examples do not have this line also. I only added one line in onBindViewHolder
From this:
if (item.nativeAd!!.callToAction == null) {
holder.callToActionView.visibility = View.INVISIBLE
} else {
holder.callToActionView.visibility = View.VISIBLE
(holder.callToActionView as Button).text = item.nativeAd!!.callToAction
}
to this:
if (item.nativeAd!!.callToAction == null) {
holder.callToActionView.visibility = View.INVISIBLE
} else {
holder.callToActionView.visibility = View.VISIBLE
(holder.callToActionView as Button).text = item.nativeAd!!.callToAction
holder.adView.callToActionView = (holder.callToActionView as Button)
}
That means I needed to attach my button as callToActionView
Related
I have 2 activities. Which I tried to convert into fragments but somehow, the code I use for the activities won't work within fragments. I get a bunch of errors.
The first activity is a card where when clicked a random text will appear and the card is flipping.
The second activity is a timer with a 3th party progress bar that the fragment gives an error.
Now my question is, how can I use 2 activities within a tabLayout since I cannot convert the activity code into a fragment?
I have been searching the internet for hours but I can only find that you can use fragments within a tabLayout.
Is there a tutorial on how to create a tabLayout with 2 activities?
I also followed the tutorial https://mkyong.com/android/android-tablayout-example/ but that didn't work.
This is my Timer activity:
Kotlin:
class NewTimerActivity : AppCompatActivity() {
enum class TimerState {
Stopped, Paused, Running
}
private lateinit var timer: CountDownTimer
private var timerLengthSeconds: Int = 30
private var timerState = TimerState.Stopped
private var secondsRemaining = 0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_new_timer)
val display = supportActionBar
display?.title = ""
display?.setDisplayHomeAsUpEnabled(true)
val fab_start = findViewById<Button>(R.id.fab_start)
val fab_pause = findViewById<Button>(R.id.fab_pause)
val fab_stop = findViewById<Button>(R.id.fab_stop)
fab_start.setOnClickListener {
startTimer()
timerState = TimerState.Running
updateButtons()
}
fab_pause.setOnClickListener {
timer.cancel()
timerState = TimerState.Paused
updateButtons()
}
fab_stop.setOnClickListener {
timer.cancel()
onTimerFinished()
updateButtons()
}
}
override fun onResume() {
super.onResume()
initTimer()
}
override fun onPause() {
super.onPause()
if (timerState == TimerState.Running) {
timer.cancel()
} else if (timerState == TimerState.Paused) {
}
PrefUtil.setPreviousTimerLengthSeconds(timerLengthSeconds, this)
PrefUtil.setSecondsRemaining(secondsRemaining, this)
PrefUtil.setTimerState(timerState, this)
}
fun initTimer() {
timerState = PrefUtil.getTimerState(this)
if (timerState == TimerState.Stopped)
setNewTimerLength()
else
setPreviousTimerLength()
secondsRemaining = if (timerState == TimerState.Running || timerState == TimerState.Paused)
PrefUtil.getSecondsRemaining(this).toInt()
else
timerLengthSeconds
if (timerState == TimerState.Running)
startTimer()
updateButtons()
updateCountdownUI()
}
private fun onTimerFinished() {
var progress_countdown = findViewById<ProgressBar>(R.id.progress_countdown)
timerState = TimerState.Stopped
setNewTimerLength()
progress_countdown.progress = 0
PrefUtil.setSecondsRemaining(timerLengthSeconds, this)
secondsRemaining = timerLengthSeconds
updateButtons()
updateCountdownUI()
}
private fun startTimer() {
timerState = TimerState.Running
timer = object : CountDownTimer((secondsRemaining * 1000).toLong(), 1000) {
override fun onFinish() = onTimerFinished()
override fun onTick(millisUntilFinished: Long) {
secondsRemaining = (millisUntilFinished / 1000).toInt()
updateCountdownUI()
}
}.start()
}
private fun setNewTimerLength() {
var progress_countdown = findViewById<ProgressBar>(R.id.progress_countdown)
var lengthInMinutes = PrefUtil.getTimerLength(this)
timerLengthSeconds = ((lengthInMinutes * 60L).toInt())
progress_countdown.max = timerLengthSeconds.toInt()
}
private fun setPreviousTimerLength() {
var progress_countdown = findViewById<ProgressBar>(R.id.progress_countdown)
timerLengthSeconds = PrefUtil.getPreviousTimerLengthSeconds(this).toInt()
progress_countdown.max = timerLengthSeconds.toInt()
}
private fun updateCountdownUI() {
var progress_countdown = findViewById<ProgressBar>(R.id.progress_countdown)
val textView_Countdown = findViewById<TextView>(R.id.timer_textview)
val minutesUntilFinished = secondsRemaining / 60
val secondsInMinutesUntilFinished = secondsRemaining - minutesUntilFinished * 60
val secondsStr = secondsInMinutesUntilFinished.toString()
textView_Countdown.text = "$minutesUntilFinished:${
if (secondsStr.length == 2) secondsStr
else "0" + secondsStr}"
progress_countdown.progress = (timerLengthSeconds - secondsRemaining).toInt()
}
private fun updateButtons() {
val fab_start = findViewById<Button>(R.id.fab_start)
val fab_pause = findViewById<Button>(R.id.fab_pause)
val fab_stop = findViewById<Button>(R.id.fab_stop)
when (timerState) {
TimerState.Running -> {
fab_start.isEnabled = false
fab_pause.isEnabled = true
fab_stop.isEnabled = true
}
TimerState.Stopped -> {
fab_start.isEnabled = true
fab_pause.isEnabled = false
fab_stop.isEnabled = false
}
TimerState.Paused -> {
fab_start.isEnabled = true
fab_pause.isEnabled = false
fab_stop.isEnabled = true
}
}
}
}
XML:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#drawable/achtergrondnowhite"
tools:context=".NewTimerActivity">
<TextView
android:id="#+id/timer_textview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:gravity="center"
android:text="30"
android:textColor="#FFF"
android:textSize="70dp" />
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="#style/Theme.KlimaatAmbitieGame.AppBarOverlay">
<androidx.appcompat.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="#style/Theme.KlimaatAmbitieGame.PopupOverlay" />
</com.google.android.material.appbar.AppBarLayout>
<me.zhanghai.android.materialprogressbar.MaterialProgressBar
android:id="#+id/progress_countdown"
style="#style/Widget.MaterialProgressBar.ProgressBar"
android:layout_width="306dp"
android:layout_height="306dp"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"
android:layout_alignParentEnd="true"
android:layout_alignParentBottom="true"
android:layout_marginStart="52dp"
android:layout_marginTop="215dp"
android:layout_marginEnd="52dp"
android:layout_marginBottom="210dp"
android:minWidth="306dp"
android:minHeight="306dp"
app:mpb_indeterminateTint="#color/white"
app:mpb_progressBackgroundTint="#color/white"
app:mpb_progressTint="#color/white"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="#+id/fab_stop"
android:layout_width="100dp"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_alignParentEnd="true"
android:layout_alignParentBottom="true"
android:layout_gravity="bottom|end"
android:layout_marginStart="274dp"
android:layout_marginEnd="27dp"
android:layout_marginBottom="131dp"
android:background="#color/black"
android:minWidth="50dp"
android:minHeight="50dp"
android:text="Stop"
android:textColor="#color/white"
app:srcCompat="#drawable/ic_stop" />
<Button
android:id="#+id/fab_pause"
android:layout_width="100dp"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_alignParentEnd="true"
android:layout_alignParentBottom="true"
android:layout_gravity="bottom|center"
android:layout_marginStart="156dp"
android:layout_marginTop="#dimen/bigger_fab_margin"
android:layout_marginEnd="151dp"
android:layout_marginBottom="131dp"
android:background="#color/black"
android:minWidth="150dp"
android:minHeight="50dp"
android:text="Pause"
android:textColor="#color/white"
app:srcCompat="#drawable/ic_pause" />
<Button
android:id="#+id/fab_start"
android:layout_width="100dp"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_alignParentEnd="true"
android:layout_alignParentBottom="true"
android:layout_gravity="bottom|start"
android:layout_marginStart="45dp"
android:layout_marginTop="#dimen/bigger_fab_margin"
android:layout_marginEnd="280dp"
android:layout_marginBottom="133dp"
android:background="#color/black"
android:minWidth="100dp"
android:minHeight="50dp"
android:text="Start"
android:textColor="#color/white" />
</RelativeLayout>
PrefUtil
class PrefUtil {
companion object {
fun getTimerLength(context: Context): Double {
//placeholder
return 0.5
}
//private var defValue: Long
private const val PREVIOUS_TIMER_LENGTH_SECONDS_ID = "com.resoconder.timer.previous_timer_length"
fun getPreviousTimerLengthSeconds(context: Context): Long {
val preferences = PreferenceManager.getDefaultSharedPreferences(context)
return preferences.getLong(PREVIOUS_TIMER_LENGTH_SECONDS_ID, 0)
}
fun setPreviousTimerLengthSeconds(seconds: Int, context: Context) {
val editor = PreferenceManager.getDefaultSharedPreferences(context).edit()
editor.putLong(PREVIOUS_TIMER_LENGTH_SECONDS_ID, seconds.toLong())
editor.apply()
}
private const val TIMER_STATE_ID = "com.resocoder.timer.timer_state"
fun getTimerState(context: Context): NewTimerActivity.TimerState {
val preferences = PreferenceManager.getDefaultSharedPreferences(context)
val ordinal = preferences.getInt(TIMER_STATE_ID, 0)
return NewTimerActivity.TimerState.values()[ordinal]
}
fun setTimerState(state: NewTimerActivity.TimerState, context: Context) {
val editor = PreferenceManager.getDefaultSharedPreferences(context).edit()
val ordinal = state.ordinal
editor.putInt(TIMER_STATE_ID, ordinal)
editor.apply()
}
private const val SECONDS_REMAINING_ID = "com.resoconder.timer.previous_timer_length"
fun getSecondsRemaining(context: Context): Long {
val preferences = PreferenceManager.getDefaultSharedPreferences(context)
return preferences.getLong(SECONDS_REMAINING_ID, 0)
}
fun setSecondsRemaining(seconds: Int, context: Context) {
val editor = PreferenceManager.getDefaultSharedPreferences(context).edit()
editor.putLong(SECONDS_REMAINING_ID, seconds.toLong())
editor.apply()
}
}
}
And this is my Card activity:
Kotlin:
class StartKaartActivity : AppCompatActivity() {
lateinit var front_anim:AnimatorSet
lateinit var back_anim:AnimatorSet
var isFront = false
#SuppressLint("ResourceType")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_start_kaart)
//Set Backbutton Bar
val display = supportActionBar
display?.title = ""
display?.setDisplayHomeAsUpEnabled(true)
display?.setDisplayUseLogoEnabled(true)
display?.setLogo(R.drawable.logo)
val scale = applicationContext.resources.displayMetrics.density
val tvstartkaart = findViewById<TextView>(com.fotf.klimaatambitiegame.R.id.tvstartkaart)
val tvstartkaartachterkant = findViewById<TextView>(com.fotf.klimaatambitiegame.R.id.tvstartkaartachterkant)
tvstartkaart.cameraDistance = 8000 * scale
tvstartkaartachterkant.cameraDistance = 8000 * scale
front_anim = AnimatorInflater.loadAnimator(applicationContext, R.anim.font_animation) as AnimatorSet
back_anim = AnimatorInflater.loadAnimator(applicationContext, R.anim.back_animation) as AnimatorSet
val Carts = arrayOf("" +
"Random Text",
)
tvstartkaart.setOnClickListener() {
if (isFront) {
front_anim.setTarget(tvstartkaart)
back_anim.setTarget(tvstartkaartachterkant)
front_anim.start()
back_anim.start()
isFront = false
} else {
val random = Carts.random()
tvstartkaart.setText(random)
front_anim.setTarget(tvstartkaartachterkant)
back_anim.setTarget(tvstartkaart)
front_anim.start()
back_anim.start()
isFront = true
}
};
}
}
XML:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#drawable/achtergrondnowhite"
tools:context=".StartKaartActivity">
<TextView
android:id="#+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="176dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="177dp"
android:paddingHorizontal="10dp"
android:text="Klik op de kaart om een nieuwe kaart te krijgen. \n\nGeef het goede antwoord op de vraag en verdien een houder."
android:textColor="#color/white"
android:textSize="20dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.498"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="#+id/tvstartkaart"
android:layout_width="350dp"
android:layout_height="450dp"
android:layout_marginTop="28dp"
android:background="#drawable/kaartstartvoorkant"
android:gravity="left|center"
android:padding="15dp"
android:paddingHorizontal="10dp"
android:text=""
android:textColor="#color/black"
android:textSize="20dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.491"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/textView" />
<TextView
android:id="#+id/tvstartkaartachterkant"
android:layout_width="350dp"
android:layout_height="450dp"
android:layout_marginTop="28dp"
android:background="#drawable/kaartstartachterkant"
android:gravity="left|center"
android:padding="15dp"
android:paddingHorizontal="10dp"
android:textColor="#color/black"
android:textSize="20dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.491"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/textView" />
</androidx.constraintlayout.widget.ConstraintLayout>
I'm trying to check what type of childrens has a linearlayout, but i keep getting true that a textview is the same as a view. How can I make the difference between a textview and a view.
In my linear layout I have a view and a text view and in the code below when the child is textview i'm getting true in both sentences(I mean I'm getting true in view is View and view is TextView if the child in that moment is textview). And I want to know thow can I get false when the sentence is view is View if the child is textview.
Here is where I'm trying to check the type of child:
private fun selectFromLayout(ll: LinearLayout, select: Boolean) {
val childCount = ll.childCount
for (i in 0 until childCount) {
val view = ll.getChildAt(i)
if (view is View) {
if (select) {
view.background = ContextCompat.getDrawable(context, R.drawable.back_select)
} else {
view.setBackgroundResource(R.color.white)
}
}
if (view is TextView) {
if (select) {
view.setTextColor(ContextCompat.getColor(context, R.color.gray_dark))
view.typeface = ResourcesCompat.getFont(context, R.font.montserrat_medium)
} else {
view.setTextColor(ContextCompat.getColor(context, R.color.gray_medium))
view.typeface = ResourcesCompat.getFont(context, R.font.montserrat_regular)
}
}
}
}
Linear layout:
<LinearLayout
android:id="#+id/btn_tab1"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="?attr/selectableItemBackground"
android:focusable="true"
android:gravity="bottom"
android:orientation="vertical">
<TextView
android:id="#+id/tv_tab1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="20dp"
android:gravity="center"
app:textSize="10sp"
app:textType="regular"
tools:text="Próximas" />
<View
android:id="#+id/tab1_shadow"
android:layout_width="match_parent"
android:layout_height="2dp" />
</LinearLayout>
It's not necessary to check is View as it always be true, since all views in android extends from View class.
private fun selectFromLayout(ll: LinearLayout, select: Boolean) {
val childCount = ll.childCount
for (i in 0 until childCount) {
val view = ll.getChildAt(i)
if (view is TextView) {
if (select) {
view.setTextColor(ContextCompat.getColor(context, R.color.gray_dark))
view.typeface = ResourcesCompat.getFont(context, R.font.montserrat_medium)
} else {
view.setTextColor(ContextCompat.getColor(context, R.color.gray_medium))
view.typeface = ResourcesCompat.getFont(context, R.font.montserrat_regular)
}
} else {
if (select) {
view.background = ContextCompat.getDrawable(context, R.drawable.back_select)
} else {
view.setBackgroundResource(R.color.white)
}
}
}
}
I would like to display a textView when my RecyclerView is empty.
I prepared this function but it doesn't work. I believe I should get the list from RecyclerView but I don't really know how.
I am in a fragment.
XML:
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/recycler_view_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="visible"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:listitem="#layout/item_list" />
<TextView
android:id="#+id/tv_no_records"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:text="#string/nothing_to_display"
android:textSize="16sp"
android:visibility="gone" />
Fragment:
private fun displayList() {
val list = listOf<Shoe>()
if (list.isEmpty()) {
binding.recyclerViewList.visibility = View.VISIBLE
binding.tvNoRecords.visibility = View.GONE
} else {
binding.recyclerViewList.visibility = View.GONE
binding.tvNoRecords.visibility = View.VISIBLE
}
}
Adding the shoe (in ViewModel):
fun addShoe(shoe: Shoe) {
viewModelScope.launch(Dispatchers.IO) {
repository.addShoes(shoe)
}
}
Many thanks,
Anna
The conditions are inverted. When list is empty you are showing recyclerView and vice versa. Simply use a not check.
Consider below:
private fun displayList() {
val list = listOf<Shoe>()
if (list.isNotEmpty()) {
binding.recyclerViewList.visibility = View.VISIBLE
binding.tvNoRecords.visibility = View.GONE
} else {
binding.recyclerViewList.visibility = View.GONE
binding.tvNoRecords.visibility = View.VISIBLE
}
}
I follow code here but failed to capture my ArFragment. Please help me.
https://codelabs.developers.google.com/codelabs/sceneform-intro/index.html?index=..%2F..io2018#15
This project's link: https://github.com/DSparda/Breakable-Cam
Always get IOException: "Failed to save bitmap to disk"
My WritingArFragment:
class FaceArFragment : ArFragment() {
override fun getSessionConfiguration(session: Session?): Config {
return Config(session).apply { augmentedFaceMode = Config.AugmentedFaceMode.MESH3D }
}
override fun getSessionFeatures(): MutableSet<Session.Feature> {
return EnumSet.of(Session.Feature.FRONT_CAMERA)
}
override fun getAdditionalPermissions(): Array<String?>? {
val additionalPermissions = super.getAdditionalPermissions()
val permissionLength =
additionalPermissions?.size ?: 0
val permissions =
arrayOfNulls<String>(permissionLength + 1)
permissions[0] = Manifest.permission.WRITE_EXTERNAL_STORAGE
if (permissionLength > 0) {
System.arraycopy(
additionalPermissions,
0,
permissions,
1,
additionalPermissions!!.size
)
}
return permissions
}
/**
* Override to turn off planeDiscoveryController. Plane trackables are not supported with the
* front camera.
*/
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
val layout = super.onCreateView(inflater, container, savedInstanceState) as FrameLayout
planeDiscoveryController.apply {
hide()
setInstructionView(null)
}
return layout
}
}
My Activity XML:
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="takePictureViewModel"
type="com.example.breakablecam.screens.takingPicture.TakePictureViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.fragment.app.FragmentContainerView
android:id="#+id/face_fragment_cointanier"
android:layout_width="match_parent"
android:layout_height="0dp"
android:paddingBottom="16dp"
app:layout_constraintBottom_toTopOf="#+id/takePhotoView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:visibility="visible">
<fragment
android:id="#+id/face_fragment"
android:name="com.example.breakablecam.screens.takingPicture.FaceArFragment"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</androidx.fragment.app.FragmentContainerView>
<ImageView
android:id="#+id/backArrow"
android:layout_width="#android:dimen/app_icon_size"
android:layout_height="#android:dimen/app_icon_size"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<ImageView
android:id="#+id/stickerView"
android:layout_width="#android:dimen/app_icon_size"
android:layout_height="#android:dimen/app_icon_size"
android:layout_marginBottom="16dp"
android:onClick="#{() -> takePictureViewModel.tapSticker()}"
android:visibility="visible"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="#+id/takePhotoView"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent" />
<ImageView
android:id="#+id/stickerView1"
android:layout_width="#android:dimen/app_icon_size"
android:layout_height="#android:dimen/app_icon_size"
android:layout_marginStart="16dp"
android:layout_marginBottom="16dp"
android:visibility="gone"
android:onClick="#{() -> takePictureViewModel.tapSticker1()}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<ImageView
android:id="#+id/takePhotoView"
android:layout_width="#android:dimen/app_icon_size"
android:layout_height="#android:dimen/app_icon_size"
android:layout_marginBottom="16dp"
android:visibility="visible"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="#+id/makeupView"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="#+id/stickerView" />
<ImageView
android:id="#+id/makeupView"
android:layout_width="#android:dimen/app_icon_size"
android:layout_height="#android:dimen/app_icon_size"
android:layout_marginBottom="16dp"
android:onClick="#{() -> takePictureViewModel.tapMakeupView()}"
android:visibility="visible"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="#+id/takePhotoView" />
<ImageView
android:id="#+id/makeupView1a"
android:layout_width="#android:dimen/app_icon_size"
android:layout_height="#android:dimen/app_icon_size"
android:layout_marginStart="16dp"
android:layout_marginBottom="16dp"
android:onClick="#{() -> takePictureViewModel.tapMakeup1aView()}"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
tools:visibility="visible" />
<ImageView
android:id="#+id/makeupView1"
android:layout_width="#android:dimen/app_icon_size"
android:layout_height="#android:dimen/app_icon_size"
android:layout_marginStart="16dp"
android:visibility="gone"
app:layout_constraintBottom_toTopOf="#id/makeupView1a"
app:layout_constraintStart_toStartOf="parent"
tools:visibility="visible" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
My Activity:
class TakePictureActivity : AppCompatActivity() {
private lateinit var viewModel: TakePictureViewModel
private lateinit var arFragment: FaceArFragment
private var modelRenderable: ModelRenderable? = null
private var meshTexture: Texture? = null
private val faceNodeMap = HashMap<AugmentedFace, AugmentedFaceNode?>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding: ActivityTakePictureBinding =
DataBindingUtil.setContentView(this, R.layout.activity_take_picture)
viewModel = ViewModelProvider(this).get(TakePictureViewModel::class.java)
binding.apply {
viewModel.apply {
setStickerViewSource(stickerView)
setMakeupViewSource(makeupView)
setTakePhotoViewSource(takePhotoView)
setMakeup1ViewSource(makeupView1)
setMakeup1aViewSource(makeupView1a)
setSticker1ViewSource(stickerView1)
setBackArrowSource(backArrow)
}
}
binding.takePictureViewModel = viewModel
binding.lifecycleOwner = this
viewModel.makeupTap.observe(this, Observer { check ->
if (check == 1) {
binding.apply {
makeupView.visibility = GONE
takePhotoView.visibility = GONE
stickerView.visibility = GONE
makeupView1.visibility = VISIBLE
makeupView1a.visibility = VISIBLE
backArrow.visibility = VISIBLE
val params =
faceFragmentCointanier.layoutParams as ConstraintLayout.LayoutParams
params.bottomToTop = R.id.makeupView1
}
viewModel.doneTapMakeup()
}
})
viewModel.stickerTap.observe(this, Observer { check ->
if (check == 1) {
binding.apply {
makeupView.visibility = GONE
takePhotoView.visibility = GONE
stickerView.visibility = GONE
stickerView1.visibility = VISIBLE
backArrow.visibility = VISIBLE
val params =
faceFragmentCointanier.layoutParams as ConstraintLayout.LayoutParams
params.bottomToTop = R.id.stickerView1
}
viewModel.doneTapSticker()
}
})
binding.apply {
backArrow.setOnClickListener {
makeupView.visibility = VISIBLE
takePhotoView.visibility = VISIBLE
stickerView.visibility = VISIBLE
makeupView1.visibility = GONE
makeupView1a.visibility = GONE
backArrow.visibility = GONE
stickerView1.visibility = GONE
backArrow.visibility = GONE
val params =
faceFragmentCointanier.layoutParams as ConstraintLayout.LayoutParams
params.bottomToTop = R.id.takePhotoView
}
}
arFragment = supportFragmentManager.findFragmentById(R.id.face_fragment) as FaceArFragment
val sceneView = arFragment.arSceneView
sceneView.cameraStreamRenderPriority = Renderable.RENDER_PRIORITY_FIRST
val scene = sceneView.scene
loadTexture(R.drawable.makeup1a)
loadModel(R.raw.fox_face)
viewModel.makeupTap1a.observe(this, Observer { check ->
when (check) {
1 -> {
scene.addOnUpdateListener {
val collection: Collection<AugmentedFace>? =
sceneView.session?.getAllTrackables(AugmentedFace::class.java)
collection?.forEach { face ->
if (!faceNodeMap.containsKey(face)) {
val faceNode = AugmentedFaceNode(face)
faceNode.apply {
setParent(scene)
faceMeshTexture = meshTexture
}
faceNodeMap[face] = faceNode
}
}
val iterator = faceNodeMap.entries.iterator()
while (iterator.hasNext()) {
val entry = iterator.next()
val face = entry.key
if (face.trackingState == TrackingState.STOPPED) {
val faceNode = entry.value
faceNode!!.setParent(null)
iterator.remove()
}
}
}
}
2 -> {
val children: List<Node> =
ArrayList(arFragment.arSceneView.scene.children)
for (node in children) {
if (node is AnchorNode) {
if (node.anchor != null) {
node.anchor?.detach()
}
}
if (node !is Camera && node !is Sun) {
node.setParent(null)
}
}
}
}
})
viewModel.sticker1Tap.observe(this, Observer { check ->
when (check) {
1 -> {
scene.addOnUpdateListener {
val collection: Collection<AugmentedFace>? =
sceneView.session?.getAllTrackables(AugmentedFace::class.java)
collection?.forEach { face ->
if (!faceNodeMap.containsKey(face)) {
val faceNode = AugmentedFaceNode(face)
faceNode.apply {
setParent(scene)
faceRegionsRenderable = modelRenderable
}
faceNodeMap[face] = faceNode
}
}
val iterator = faceNodeMap.entries.iterator()
while (iterator.hasNext()) {
val entry = iterator.next()
val face = entry.key
if (face.trackingState == TrackingState.STOPPED) {
val faceNode = entry.value
faceNode!!.setParent(null)
iterator.remove()
}
}
}
}
2 -> {
val children: List<Node> =
ArrayList(arFragment.arSceneView.scene.children)
for (node in children) {
if (node is AnchorNode) {
if (node.anchor != null) {
node.anchor?.detach()
}
}
if (node !is Camera && node !is Sun) {
node.setParent(null)
}
}
}
}
})
binding.takePhotoView.setOnClickListener {
takePhoto()
}
}
private fun loadTexture(tex: Int) {
Texture.builder()
.setSource(this, tex)
.build()
.thenAccept { texture -> meshTexture = texture }
}
private fun loadModel(mod: Int) {
ModelRenderable.builder()
.setSource(this, mod)
.build()
.thenAccept { model ->
model.apply {
isShadowCaster = false // optional
isShadowReceiver = false
}
modelRenderable = model
}
}
private fun generateFilename(): String? {
val date =
SimpleDateFormat("yyyyMMddHHmmss", Locale.getDefault())
.format(Date())
return Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_PICTURES
).toString() + File.separator + "Sceneform/" + date + "_screenshot.jpg"
}
#Throws(IOException::class)
private fun saveBitmapToDisk(bitmap: Bitmap, filename: String) {
val out = File(filename)
if (!out.parentFile.exists()) {
out.parentFile.mkdirs()
}
try {
FileOutputStream(filename).use { outputStream ->
ByteArrayOutputStream().use { outputData ->
bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputData)
outputData.writeTo(outputStream)
outputStream.flush()
outputStream.close()
}
}
} catch (ex: IOException) {
throw IOException("Failed to save bitmap to disk", ex)
}
}
private fun takePhoto() {
val filename = generateFilename()
val view: ArSceneView = arFragment.getArSceneView()
// Create a bitmap the size of the scene view.
val bitmap = Bitmap.createBitmap(
view.width, view.height,
Bitmap.Config.ARGB_8888
)
// Create a handler thread to offload the processing of the image.
val handlerThread = HandlerThread("PixelCopier")
handlerThread.start()
// Make the request to copy.
PixelCopy.request(view, bitmap, { copyResult ->
if (copyResult === PixelCopy.SUCCESS) {
try {
saveBitmapToDisk(bitmap, filename!!)
} catch (e: IOException) {
val toast = Toast.makeText(
this, e.toString(),
Toast.LENGTH_LONG
)
toast.show()
return#request
}
val snackbar = Snackbar.make(
findViewById(android.R.id.content),
"Photo saved", Snackbar.LENGTH_LONG
)
snackbar.setAction(
"Open in Photos"
) { v: View? ->
val photoFile = File(filename)
val photoURI = FileProvider.getUriForFile(
this,
this.getPackageName()
.toString() + ".ar.codelab.name.provider",
photoFile
)
val intent = Intent(Intent.ACTION_VIEW, photoURI)
intent.setDataAndType(photoURI, "image/*")
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
startActivity(intent)
}
snackbar.show()
} else {
val toast = Toast.makeText(
this,
"Failed to copyPixels: $copyResult", Toast.LENGTH_LONG
)
toast.show()
}
handlerThread.quitSafely()
}, Handler(handlerThread.looper))
}
}
Thank everyone so much.
try add
<application android:requestLegacyExternalStorage="true" ... > ... </application>
to your manifest.
I assume that this is related to the upcoming scoped storage enforcement in Android 11.
You have to add the tags in your AndroidManifest.xml specified in the codelabs(permissions and file provider.
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="${applicationId}.ar.codelab.name.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/paths"/>
</provider>
Then you have to migrate from using File to using Uris and ContentResolvers because Android 10 deprecated the File functionality and you have to use MediaStore.
https://developer.android.com/training/data-storage/shared/media
Its a simle app that shows some information about any given number like if its odd or prime.
Like the title said app works fine when any value is given but as soon as i push buton without it app crashes.
I have tried many thing but nothing changes. Here is code:
package com.example.numbers
import android.os.Bundle
import android.widget.Button
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
fun oddOrEven(num: Int): String
{
return when (num % 2 == 0)
{
true -> "Number is: even\n"
false -> "Number is: odd\n"
}
}
fun isPrime(num: Int): String
{
var flag = false
for (i in 2..num / 2 + 1)
{
if (num % i == 0)
{
flag = true
break
}
}
return when (flag)
{
false -> "Number is: prime\n"
true -> "Number is: not prime\n"
}
}
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val textButton = findViewById<Button>(R.id.printButton)
val entry = findViewById<TextView>(R.id.entry)
val textView = findViewById<TextView>(R.id.textView)
textButton?.setOnClickListener {
textView.text = ""
val num = entry.text.toString().toInt()
var toPrint = ""
toPrint += oddOrEven(num) + isPrime(num)
textView.text = toPrint
}
}
}
Here is XML
<TextView
android:id="#+id/text1"
android:layout_width="266dp"
android:layout_height="62dp"
android:layout_marginTop="28dp"
android:gravity="center"
android:text="Podaj liczbę"
android:textSize="36sp"
app:fontFamily="#font/anonymous_pro"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.496"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<EditText
android:id="#+id/entry"
android:layout_width="291dp"
android:layout_height="57dp"
android:ems="10"
android:gravity="center"
android:inputType="number"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/text1" />
<Button
android:id="#+id/printButton"
android:layout_width="180dp"
android:layout_height="42dp"
android:layout_marginTop="36dp"
android:text="Ok"
android:textColor="#000000"
android:textColorHint="#000000"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.497"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/entry" />
<TextView
android:id="#+id/textView"
android:layout_width="0dp"
android:layout_height="400dp"
android:layout_marginStart="32dp"
android:layout_marginLeft="32dp"
android:layout_marginTop="52dp"
android:layout_marginEnd="32dp"
android:layout_marginRight="32dp"
android:textSize="24sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/printButton"
app:layout_constraintVertical_bias="0.0" />
</androidx.constraintlayout.widget.ConstraintLayout>
I hope my problem is clear. I will answer any question.
Without entering a number entry.text.toString() will return "" (an empty string) and "".toInt() results in an exception similar or equal to this:
Exception in thread "main" java.lang.NumberFormatException: For input string: ""
at java.lang.NumberFormatException.forInputString (NumberFormatException.java:65)
at java.lang.Integer.parseInt (Integer.java:592)
at java.lang.Integer.parseInt (Integer.java:615)
I suggest replacing toInt() with toIntOrNull() (which will also prevent crashes if you enter e.g. a letter or a decimal number) and modify your click listener as following:
textButton?.setOnClickListener {
textView.text = ""
val num = entry.text.toString().toIntOrNull()
val toPrint = if (num != null) {
oddOrEven(num) + isPrime(num)
} else {
""
}
textView.text = toPrint
}