I would use the BadgeDrawable in my Android app on a Button, the issue is that when i'm trying to set to the button layout the badgeDrawable via attachBadgeDrawable i get an error on it which says:
his declaration is opt-in and its usage should be marked with '#com.google.android.material.badge.ExperimentalBadgeUtils' or '#OptIn(markerClass = com.google.android.material.badge.ExperimentalBadgeUtils.class)'
The code where i use that piece of code is the following:
btnInvia.viewTreeObserver.addOnGlobalLayoutListener(
object : OnGlobalLayoutListener {
override fun onGlobalLayout() {
val badgeDrawable = BadgeDrawable.create(requireContext())
badgeDrawable.number = corpo
badgeDrawable.verticalOffset = 20
badgeDrawable.horizontalOffset = 15
BadgeUtils.attachBadgeDrawable(badgeDrawable, btnInvia, layoutInvia)
btnInvia.viewTreeObserver.removeOnGlobalLayoutListener(this)
}
}
)
if it could be usefull the min SDK is 24.
The class BadgeUtils is marked with the androidx annotation #Experimental. In this way it is reported the usages of experimental API in this case with a level = ERROR.
In your method you have to use one of these annotations to suppress the report:
#ExperimentalBadgeUtils
#UseExperimental(markerClass = ExperimentalBadgeUtils::class)
fun onCreate(savedInstanceState: Bundle?) {
//...
btnInvia.viewTreeObserver.addOnGlobalLayoutListener(
//..
)
}
You can also use the kotlin annotation #OptIn:
#OptIn(ExperimentalBadgeUtils::class)
fun onCreate(savedInstanceState: Bundle?) {
//...
btnInvia.viewTreeObserver.addOnGlobalLayoutListener(
//..
)
}
Related
As Display methods are deprecated in Android 12, I am planning to use Jetpack's backward compatible WindowManager library succeeding Display.
However I am not sure whether I face an issue if I directly access the sizes of a screen in Activity like below:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val windowMetrics = WindowMetricsCalculator.getOrCreate()
.computeCurrentWindowMetrics(this#WindowMetricsActivity)
val width = windowMetrics.bounds.width()
val height = windowMetrics.bounds.height()
}
Because Google's sample code insists using onConfigurationChanged method by using a utility container view like below:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Add a utility view to the container to hook into
// View.onConfigurationChanged.
// This is required for all activities, even those that don't
// handle configuration changes.
// We also can't use Activity.onConfigurationChanged, since there
// are situations where that won't be called when the configuration
// changes.
// View.onConfigurationChanged is called in those scenarios.
// https://issuetracker.google.com/202338815
container.addView(object : View(this) {
override fun onConfigurationChanged(newConfig: Configuration?) {
super.onConfigurationChanged(newConfig)
logCurrentWindowMetrics("Config.Change")
}
})
}
#SuppressLint("NotifyDataSetChanged")
private fun logCurrentWindowMetrics(tag: String) {
val windowMetrics = WindowMetricsCalculator.getOrCreate()
.computeCurrentWindowMetrics(this#WindowMetricsActivity)
val width = windowMetrics.bounds.width()
val height = windowMetrics.bounds.height()
adapter.append(tag, "width: $width, height: $height")
runOnUiThread {
adapter.notifyDataSetChanged()
}
}
In our project, we directly access the sizes of screens and I am wondering how we migrate to accessing them after onConfigurationChanged invokes and emits the size values by using MAD skills.
Any approaches will be appreciated
I use the following method to get a screen size starting from Android 11 (API level 30):
fun getScreenSize(context: Context): Size {
val metrics: WindowMetrics = context.getSystemService(WindowManager::class.java).currentWindowMetrics
return Size(metrics.bounds.width(), metrics.bounds.height())
}
I have an android module (ComposeLib) as part of the same project as the app. It's just to test out using Compose from a library. In the Project Structure dialog I added ComposeLib as an implementation dependency of app.
build.gradle (:app) contains...
dependencies {
...
implementation "androidx.compose.ui:ui:$compose_version"
implementation "androidx.compose.material:material:$compose_version"
implementation "androidx.compose.ui:ui-tooling-preview:$compose_version"
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.1'
implementation 'androidx.activity:activity-compose:1.4.0-rc01'
implementation "com.google.accompanist:accompanist-appcompat-theme:0.16.0"
implementation project(path: ':ComposeLib')
...
}
Atoms.kt in ComposeLib consists of...
class Atoms {
#Composable
fun CounterButton(count: Int, updateCount: (Int) -> Unit) {
Button( onClick = {updateCount(count+1)},
modifier = Modifier
.background(MaterialTheme.colors.secondary)){
Text("Clicked $count times")
}
}
}
Then in MainActivity.kt I am trying to use CounterButton...
import com.example.composelib.Atoms
class MainActivity : AppCompatActivity() {
lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
val myComposeView = findViewById<ComposeView>(R.id.composeView)
myComposeView.setContent {
val counter = remember{ mutableStateOf(0) }
AppCompatTheme {
CounterButton( // <== Unresolved Reference!?
count = counter.value,
updateCount = {newCount -> counter.value = newCount})
}
}
}
}
As you can see in the lower left of the screenshot the app cant find CounterButton from ComposeLib.Atoms. Any idea why?
This code works if I put CounterButton() in the app in MainActivity, so it's not a Jetpack problem it's a build configuration problem.
I also tried qualifying the call to CounterButton every way I could think of (Atoms.CounterButton, public.example.composelib.Atoms.CounterButton, etc). Even code completion doesn't recognize it.
How do I reference a #Composable function from another module in the same project?
You've defined your Composable inside class Atoms for some reason, so this function should be called on a class instance.
It's totally fine to define composable functions without any classes, just like
#Composable
fun CounterButton(count: Int, updateCount: (Int) -> Unit) {
}
It's already in some package so I don't think any container is much needed. But in case you wanna add some kind of modularity, you can replace class with object, in this case you'll be able to call it as Atoms.CounterButton
i don't know why but i get this error:
e: C:\Users\User\AndroidStudioProjects\StorageManagementTheThird\app\src\main\java\com\example\storagemanagementthethird\AddItemActivity.kt: (15, 35): Type inference failed: Not enough information to infer parameter T in fun <T : View!> findViewById(p0: Int): T!
in this code:
class AddItemActivity: AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.add_item)
val addItemSubmitButton = findViewById(R.id.addItemSubmitButton)
addItemSubmitButton.setOnClickListener {
val editTextItemName: EditText = findViewById(R.id.editTextItemName)
//alles wat we aanpassen in storage en opslaan
val database = getSharedPreferences("database", Context.MODE_PRIVATE)
database.edit().apply {
putString("savedItemName", editTextItemName.text.toString())
}.apply()
}
}
}
please help me if you can :)
You should change this line
val addItemSubmitButton = findViewById(R.id.addItemSubmitButton)
like below
val addItemSubmitButton = findViewById<View>(R.id.addItemSubmitButton)
findViewById takes a type, so it can return the correct type of view - in this case you should probably use findViewById<Button> since that's what the view it meant to be, but it doesn't really matter since you can set a click listener on any type of View
If you actually specify the type on the val declaration, like val addItemSubmitButton: Button then the compiler can infer the type for the findViewById call, and you don't need the type in the diamond brackets (it'll be greyed out and you'll get a suggestion to delete it). That's why your editTextItemName lookup is fine - you're specifying the type for that one
I'm starting out with kotlin. and I'm having trouble understanding OnClickListener. Here After setting the initial health level to 10, I need to reduce the health level by 1 and display it. So far I have initiliased the healthlevel and set the onclick, but How do declare the function to reduce it by 1 and call it when the button is clicked?
val TAG = "MyMessage"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
Log.i("LIFECYCLE", "OnCreate")
}
private var healthLevel: Int = 10 //Set the initial health level to 10
private lateinit var healthLevelTextView: TextView
private lateinit var sneezeBtn: Button
private lateinit var takeMedicationButton: Button
private lateinit var blowNoseButton: Button
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putInt("Answer", healthLevel)
Log.i(TAG, "Called SaveInstanceState()")
}
sneezeBtn.setOnClickListener{ _ ->
// the function goes here
}
Vitor has the answer, but just as a couple of alternatives...
You might want to create a function to update and display your value together:
fun setHealth(health: Int) {
healthLevel = health
healthLevelTextView.text = healthLevel
}
That couples the update with the display change, so the two things always happen together. And if you always set healthLevel with this function (instead of setting the variable directly), the display and value will always be in sync
override fun onCreate(savedInstanceState: Bundle?) {
// set your view variables, so the TextView is ready to update
setHealth(INITIAL_HEALTH_VALUE)
And if you like, Kotlin lets us put a setter function on the variable itself
var healthLevel: Int = 10
set(value) {
field = value
healthLevelTextView.text = health
}
so every time you change the value of healthLevel, it also updates the display. You can use the observable delegate too
var healthLevel: Int by Delegates.observable(10) { _, oldValue, newValue ->
healthLevelTextView.text = newValue
}
These are more advanced than just setting the value and updating the view yourself, just pointing out that they exist as alternatives and ways to keep your logic in one place. Also with these examples, they only run the code after you first set a value - they have a default of 10 in both cases, but initialising that default won't run the setter code or the observable function. So you'd still need to go healthLevel = 10 to get the text to display the initial value.
You can change your healthLevel variable by using:
sneezeBtn.setOnClickListener {
healthLevel--
healthLevelTextView.setText(healthLevel.toString())
}
As a side note, if you use lateinit you lose one of the best features of Kotlin, which is Null Safety. If you don't know how to use that yet, I recommend you to start learning it as soon as possible.
You can also use a very nice feature of Android with kotlin, where the objects for your views are generated automatically in your Activity, you just have to type your XML views id, instead of having to use findViewById everywhere.
I assume, your code sample is within an Activity class and you have a layout file activity_main.xml which looks something like this:
...
<TextView
android:id="#+id/healthLevelTextViewId"
...
/>
<Button
android:id="#+id/sneezeBtnId"
...
/>
...
This would be the code with your desired functionality:
class YourActivity : Activity {
private var healthLevel: Int = 10
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// retrieve references to the text view and button
val healthLevelTextView = findViewById<TextView>(R.id.healthLevelTextViewId)
val sneezeBtn = findViewById<Button>(R.id.sneezeBtnId)
// the OnClickListener must be initialized within a method body, as it is not a method itself.
sneezeBtn.setOnClickListener { _ ->
// implementation from the answer from Vitor Ramos
healthLevel--
healthLevelTextView.setText(healthLevel.toString())
}
}
}
You can further improve the code:
Add apply plugin: 'kotlin-android-extensions' to your app's build.gradle file. Then you can reference your views directly using their IDs. So instead of
val sneezeBtn = findViewById<Button>(R.id.sneezeBtnId)
sneezeBtn.setOnClickListener { ... }
you can use the Id directly like this:
sneezeBtnId.setOnClickListener { ... }
AndroidStudio will give you a hint to add an import for sneezeBtnId.
Use View Models, LiveData and Data Binding. This is the recommended, but a little bit more advanced technique.
The docs of AndroidX Navigation currently mostly cover usage from xml.
I'd like to see an example of programmatic usage with Kotlin, with Fragments (because I'm not aware of another navigator at the moment).
Here's a simple example of how one can use AndroidX Navigation programmatically using Fragments with the KTX artifacts:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val container = frameLayout(id = R.id.content) // From Splitties Views DSL. Equivalent to FrameLayout().apply { id = R.id.content }
setContentView(container)
// Add the NavHostFragment if needed
if (savedInstanceState == null) supportFragmentManager.transaction(now = true) {
val fragment = NavHostFragment()
add(R.id.content, fragment)
setPrimaryNavigationFragment(fragment)
}
// Use the Kotlin extension from the -ktx dependencies
// to find the NavController for the given view ID.
val navController = findNavController(R.id.content)
// Create the graph using the Kotlin DSL, setting it on the NavController
navController.graph = navController.createGraph(startDestination = R.id.nav_dest_main) {
fragment<MainFragment>(R.id.nav_dest_main) {
label = TODO("Put an actual CharSequence")
}
fragment<SomeFragment>(R.id.nav_dest_some_fragment) {
label = TODO("Put an actual CharSequence")
}
}
}
In addition to the Louis example, take a look at the official nav-component guide - Build a graph programmatically using the Kotlin DSL.