I have 1 custom view that extends ConstraintLayout and contains 1 EditText and 2 TextViews
On my custom view i define this attr (and others) :
<attr name="Text" format="string" />
and i use it like :
app:Text="#={login.email}"
Inside my custom view i define :
companion object {
#JvmStatic #BindingAdapter("Text")
fun setText(nMe : View, nText: String) {
nMe.nInput.setText(nText)
}
#InverseBindingAdapter(attribute = "Text")
fun getText(nMe : View) : String {
return nMe.nInput.text.toString()
}
witch works fine in one-way binding
app:Text="#{login.email}"
But when i try to use it in 2-way binding i get erros pointing to ActivityLoginBinding.java java.lang.String callbackArg_0 = mBindingComponent.null.getText(mEmail);
What to do to get 2-way binding?
L.E : After some research i end up with this :
#InverseBindingMethods(InverseBindingMethod(type =
CustomInput::class,attribute = "bind:Text",event =
"bind:textAttrChanged",method = "bind:getText"))
class CustomEditTextBinder {
companion object {
#JvmStatic
#BindingAdapter(value = ["textAttrChanged"])
fun setListener(editText: CustomInput, listener: InverseBindingListener?) {
if (listener != null) {
editText.nInput.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {
}
override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {
}
override fun afterTextChanged(editable: Editable) {
listener.onChange()
}
})
}
}
#JvmStatic
#InverseBindingAdapter(attribute = "Text")
fun getText(nMe: CustomInput): String {
return nMe.nInput.text.toString()
}
#JvmStatic
#BindingAdapter("Text")
fun setText(editText: CustomInput, text: String?) {
text?.let {
if (it != editText.nInput.text.toString()) {
editText.nInput.setText(it)
}
}
}
}
}
But right now i get :
Could not find event TextAttrChanged
I think all you need is event = "android:textAttrChanged".
This works for me (set text to empty String if it is 0):
object DataBindingUtil {
#BindingAdapter("emptyIfZeroText")
#JvmStatic
fun setText(editText: EditText, text: String?) {
if (text == "0" || text == "0.0") editText.setText("") else editText.setText(text)
}
#InverseBindingAdapter(attribute = "emptyIfZeroText", event = "android:textAttrChanged")
#JvmStatic
fun getText(editText: EditText) = editText.text.toString()
}
Related
I am writing a code that deletes the item from the database when the button of the popup menu is pressed , and deletes it according to the recycler viewer.
selected item is deleted from the database, but not from the recycler view.
error code: No error code
PrintActivity.kt
class PrintActivity : AppCompatActivity() {
val helper = SqliteHelper(this, "myDB.sql", 1)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val vBinding = ActivityPrintBinding.inflate(layoutInflater)
setContentView(vBinding.root)
var recyclerViewAdapter = CustomAdapter()
recyclerViewAdapter.listData = helper.select()
vBinding.myRecyclerView.adapter = recyclerViewAdapter
vBinding.myRecyclerView.layoutManager = LinearLayoutManager(this)
vBinding.myRecyclerView.addItemDecoration(
DividerItemDecoration(this, DividerItemDecoration.VERTICAL)
)
}
inner class CustomAdapter : RecyclerView.Adapter<CustomAdapter.Holder>(){
var listData = ArrayList<questionType>()
inner class Holder(val vBinding: QuestionLayoutRecyclerBinding) :
RecyclerView.ViewHolder(vBinding.root){
fun setData(id:Int?, question: String, answer: String, exp: String) {
vBinding.printId.text=id.toString()
vBinding.myLinear.setOnClickListener {
var myPopupMenu = PopupMenu(this#PrintActivity, it)
menuInflater?.inflate(R.menu.menu, myPopupMenu.menu)
var listener = PopupMenuListener()
myPopupMenu.setOnMenuItemClickListener(listener)
myPopupMenu.show()
}
}
inner class PopupMenuListener:PopupMenu.OnMenuItemClickListener{
override fun onMenuItemClick(p0: MenuItem?): Boolean {
listData[adapterPosition].id?.let { helper.delete(it) }
//here
this#CustomAdapter.notifyDataSetChanged()
return false
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CustomAdapter.Holder {
val vBinding = QuestionLayoutRecyclerBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
return Holder(vBinding)
}
override fun onBindViewHolder(holder: Holder, position: Int) {
val question = listData[position]
holder.setData(question.id, question.question, question.answer, question.exp)
}
override fun getItemCount(): Int {
return listData.size
}
}
}
You can try this approach using higher order function as callback mechanism.
One recommendation I would give is you can try to avoid these nested inner classes and make into separate files. Hope it works
inner class CustomAdapter : RecyclerView.Adapter<CustomAdapter.Holder>(){
var listData = ArrayList<questionType>()
fun deleteItem(position: Int) { //Function to remove recyclerview item
listData.remove(position)
notifyItemRemoved(position)
notifyItemRangeChanged(position, getItemCount())
}
inner class Holder(val vBinding: QuestionLayoutRecyclerBinding) :
RecyclerView.ViewHolder(vBinding.root){
fun setData(id:Int?, question: String, answer: String, exp: String,itemRemovedCallback:(a:Int)->Unit) { //higher order callback function
vBinding.printId.text=id.toString()
vBinding.myLinear.setOnClickListener {
var myPopupMenu = PopupMenu(this#PrintActivity, it)
menuInflater?.inflate(R.menu.menu, myPopupMenu.menu)
var listener = PopupMenuListener(itemRemovedCallback) //Passed to popupmenu listener
myPopupMenu.setOnMenuItemClickListener(listener)
myPopupMenu.show()
}
}
inner class PopupMenuListener(val itemRemovedCallback:(a:Int)->Unit):PopupMenu.OnMenuItemClickListener{
override fun onMenuItemClick(p0: MenuItem?): Boolean {
listData[adapterPosition].id?.let { helper.delete(it) }
itemRemovedCallback.invoke(adapterPosition) //Calling function. It will pass position to function in adapter
return false
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CustomAdapter.Holder {
val vBinding = QuestionLayoutRecyclerBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
return Holder(vBinding)
}
override fun onBindViewHolder(holder: Holder, position: Int) {
val question = listData[position]
holder.setData(question.id, question.question, question.answer, question.exp,::deleteItem) //Passing function as higher order function to viewholder
}
override fun getItemCount(): Int {
return listData.size
}
}
I've 3 Fragment - F1, F2, and F3. I've attached them in a PagerAdaper (viewPager2). The initialization of the PagerAdapter in PagerFragment (in onCreateView) is -
binding = PagerFargmentBinding.inflate(inflater, container, false)
adapter = PagerAdapter(this)
binding?.viewPager?.adapter = adapter
binding?.viewPager?.isSaveEnabled = false
Now, My PagerAdapter Implementation -->
class PagerAdapter(
fragment: PagerFragment,
) : FragmentStateAdapter(fragment) {
private var nextFragment: BaseFragment? = null
private var baseFragment: BaseFragment = F1()
override fun getItemCount() = 2
override fun createFragment(position: Int): Fragment {
return when (position) {
0 -> {
baseFragment
}
else -> {
nextFragment ?: DummyDialogFragment()
}
}
}
fun setNextFragment(page: Int) {
nextFragment = null
nextFragment = if (page == 1) {
F2()
} else if (page == 2) {
F3()
} else {
null
}
}
}
Now From my Pager Fragment I'm setting the next fragment -
fun navigateTo(page: Int) {
adapter.setNextFragment(page)
if (page == 0) {
binding?.viewPager?.currentItem = page
} else {
binding?.viewPager?.currentItem = 1
}
}
And From F1 fragment I'm calling this function with 1 and 2. This is working fine for the first time.
If First time, If I load F2 fragment as a nextFragment then F3 fragment is not displaying. I've tried debugging and found out that the nextFragment value is updating but somehow the added fragment is not being removed from the pager Adapter.
Is there any way so that I can add / remove fragment dynamically?
I've taken implementation idea from this block - https://intensecoder.com/android-swipe-fragments-with-viewpager2-in-kotlin/
Well after quite a bit of exploring over stackoverflow and then the help of one of my peer, I've figure out the solution. The solution is -
Actually the problem was, the createFragment() was not triggering
after the first time. So I've to override two function of
StatePagerAdapter - getItemViewType() and getItemId(). My Implementation
now -
class PagerAdapter(
fragmentManager: FragmentManager,
lifecycle: Lifecycle
) : FragmentStateAdapter(fragmentManager, lifecycle) {
private var viewType: Int = 0
override fun getItemCount() = 2
override fun createFragment(position: Int): Fragment {
return when (position) {
0 -> {
F1()
}
1 -> {
F2()
}
else -> {
F3()
}
}
}
override fun getItemViewType(position: Int): Int {
return when (position) {
0 -> 0
else -> viewType
}
}
override fun getItemId(position: Int): Long {
return when (position) {
0 -> 0
else -> viewType.toLong()
}
}
fun setNextFragment(page: Int) {
this.viewType = page
}
}
and Calling this from pagger fragment -
fun navigateTo(page: Int) {
adapter.setNextFragment(page)
if (page == 0) {
binding?.viewPager?.currentItem = page
} else {
binding?.viewPager?.currentItem = 1
}
}
The setNextFragment() assigning the desired F2() or F3() fragment. And by the help of the override implementation on getItemId() and getItemViewType() the createFragment() is getting called each time and thus I can add / remove fragment dynamically.
I have a bottom navigation view which I have implemented in androidx navigation, but the problem is it is showing me the error of
java.lang.IllegalStateException: Fragment already added: BaseFragment{27d5b00 (2156a830-7756-4fc9-bc63-7c6f3d6705f0) id=0x7f08008c android:switcher:2131230860:0}
I have a base fragment which provides views to different fragments
class MainActivity : AppCompatActivity(),
ViewPager.OnPageChangeListener,
BottomNavigationView.OnNavigationItemReselectedListener,
BottomNavigationView.OnNavigationItemSelectedListener {
// overall back stack of containers
private val backStack = Stack<Int>()
// list of base destination containers
private val fragments = listOf(
BaseFragment.newInstance(R.layout.content_home_base, R.id.toolbar_home, R.id.nav_host_home),
BaseFragment.newInstance(R.layout.content_library_base, R.id.toolbar_library, R.id.nav_host_library),
BaseFragment.newInstance(R.layout.content_settings_base, R.id.toolbar_settings, R.id.nav_host_settings))
// map of navigation_id to container index
private val indexToPage = mapOf(0 to R.id.home, 1 to R.id.library, 2 to R.id.settings)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// setup main view pager
main_pager.addOnPageChangeListener(this)
main_pager.adapter = ViewPagerAdapter()
main_pager.post(this::checkDeepLink)
main_pager.offscreenPageLimit = fragments.size
// set bottom nav
bottom_nav.setOnNavigationItemSelectedListener(this)
bottom_nav.setOnNavigationItemReselectedListener(this)
// initialize backStack with elements
if (backStack.empty()) backStack.push(0)
}
/// BottomNavigationView ItemSelected Implementation
override fun onNavigationItemSelected(item: MenuItem): Boolean {
val position = indexToPage.values.indexOf(item.itemId)
if (main_pager.currentItem != position) setItem(position)
return true
}
override fun onNavigationItemReselected(item: MenuItem) {
val position = indexToPage.values.indexOf(item.itemId)
val fragment = fragments[position]
fragment.popToRoot()
}
override fun onBackPressed() {
val fragment = fragments[main_pager.currentItem]
val hadNestedFragments = fragment.onBackPressed()
// if no fragments were popped
if (!hadNestedFragments) {
if (backStack.size > 1) {
// remove current position from stack
backStack.pop()
// set the next item in stack as current
main_pager.currentItem = backStack.peek()
} else super.onBackPressed()
}
}
/// OnPageSelected Listener Implementation
override fun onPageScrollStateChanged(state: Int) {}
override fun onPageScrolled(p0: Int, p1: Float, p2: Int) {}
override fun onPageSelected(page: Int) {
val itemId = indexToPage[page] ?: R.id.home
if (bottom_nav.selectedItemId != itemId) bottom_nav.selectedItemId = itemId
}
private fun setItem(position: Int) {
main_pager.currentItem = position
backStack.push(position)
}
private fun checkDeepLink() {
fragments.forEachIndexed { index, fragment ->
val hasDeepLink = fragment.handleDeepLink(intent)
if (hasDeepLink) setItem(index)
}
}
inner class ViewPagerAdapter : FragmentPagerAdapter(supportFragmentManager) {
override fun getItem(position: Int): Fragment = fragments[position]
override fun getCount(): Int = fragments.size
}
}
Base Fragment Class :
class BaseFragment: Fragment() {
private val defaultInt = -1
private var layoutRes: Int = -1
private var toolbarId: Int = -1
private var navHostId: Int = -1
private val appBarConfig = AppBarConfiguration(rootDestinations)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
arguments?.let {
layoutRes = it.getInt(KEY_LAYOUT)
toolbarId = it.getInt(KEY_TOOLBAR)
navHostId = it.getInt(KEY_NAV_HOST)
} ?: return
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
// Inflate the layout for this fragment
return if (layoutRes == defaultInt) null
else inflater.inflate(layoutRes, container, false)
}
override fun onStart() {
super.onStart()
// return early if no arguments were parsed
if (toolbarId == defaultInt || navHostId == defaultInt) return
// setup navigation with toolbar
val toolbar = requireActivity().findViewById<Toolbar>(toolbarId)
val navController = requireActivity().findNavController(navHostId)
NavigationUI.setupWithNavController(toolbar, navController, appBarConfig)
// NavigationUI.setupWithNavController(toolbar,navController)
}
fun onBackPressed(): Boolean {
return requireActivity()
.findNavController(navHostId)
.navigateUp(appBarConfig)
}
fun popToRoot() {
val navController = requireActivity().findNavController(navHostId)
navController.popBackStack(navController.graph.startDestination, false)
}
fun handleDeepLink(intent: Intent) = requireActivity().findNavController(navHostId).handleDeepLink(intent)
companion object {
private const val KEY_LAYOUT = "layout_key"
private const val KEY_TOOLBAR = "toolbar_key"
private const val KEY_NAV_HOST = "nav_host_key"
fun newInstance(layoutRes: Int, toolbarId: Int, navHostId: Int) = BaseFragment().apply {
arguments = Bundle().apply {
putInt(KEY_LAYOUT, layoutRes)
putInt(KEY_TOOLBAR, toolbarId)
putInt(KEY_NAV_HOST, navHostId)
}
}
}
}
I tried empty the stack first but that did not work, i have three navgraphs for my three viewpager elements/ fragments.
We reverted back back to 2.1.0 (nav library) and it worked again
Error is not quite connected with Navigation library. It's most probably fragment's error itself. Try testing that fragment in empty activity without using Navigation.
Navigation library is somehow treating error wrongly.
p.s. in my case it was just and view's id missing in layout file.
Thanks for the support, I found the solution I created different navigation graphs for each page.
I want check that the child class of SignalPayload implements the interface IConvertible. How can I do that?
sealed class SignalPayload {
companion object {
fun trueTypeInstance(type: KClass<SignalPayload>) : SignalPayload? {
// if (*** __ KClass<SignalPayload> implemented IConvertible ___ **)
......
}
}
}
object Empty : SignalPayload()
data class BadData(val message: String = "BAD") : SignalPayload() {
override fun toString(): String {
return message
}
}
data class payloadString(private var payload: String = "") : SignalPayload(), IConvertible
data class payloadInt(private var payload: Int = 0) : SignalPayload(), IConvertible
data class payloadFloat(private var payload: Float = 0F) : SignalPayload(), IConvertible
data class payloadBool(private var payload: Boolean = false) : SignalPayload(), IConvertible
interface IConvertible {
val asBool: Boolean
val asInt: Int
val asFloat: Float
val asString: String
fun setFromPayload(data: IConvertible)
}
How can I do that?
UPD
That I had think use it.
Class SignalChannel know what field mPayload needed (through property val type: ???). At first mPayload equal Empty
interface IArriving {
fun onNewPayload(data: SignalPayload, sender: IArriving?)
}
interface IConvertible {
val asBool: Boolean
val asInt: Int
val asFloat: Float
val asString: String
fun setFromPayload(data: IConvertible)
}
class SignalChannel(val idx: Int, val type: ???, val name: String = "") : IArriving {
var mPayload: SignalPayload = Empty
// when new data arriving - need converting for `type` and refresh field mPayload
override fun onNewPayload(data: SignalPayload, sender: IArriving?) {
if ( mPayload is Empty ) {
mPayload = SignalPayload.trueTypeInstance(type) // that's my problem
}
mPayload.setFromPayload(data)
}
}
You could make use of proper generics in order to confine the method to types that are both SignalPayload and IConverible:
fun <T> trueTypeInstance(): SignalPayload?
where T : IConvertible, T : SignalPayload {
TODO()
}
Called like this:
SignalPayload.trueTypeInstance<payloadString>() //OK
SignalPayload.trueTypeInstance<Empty>() //Fails
SignalPayload.trueTypeInstance<String>() //Fails
Not sure about your use case though...
I have the following function to access a property's delegate. It uses Kotlin reflection to get a property's name and Java reflection to get the field.
fun Any.getDelegate<T>(prop: KProperty<T>): Any {
return javaClass.getDeclaredField("${prop.name}\$delegate").let {
it.setAccessible(true)
it.get(this)
}
}
The method is used like this:
val delegate = a.getDelegate(A::b)
However, I would prefer to use it like this:
val delegate = a.b.delegate
The problem with the code above is getting the property name of a.b and getting the instance a from a.b. From what I know about Kotlin, this is probably not possible, however I'd like to see if I can clean up my function at all.
To give a bigger picture of what I'm trying do here's my complete code. I want an observable delegate to which I can add and remove observers using the delegate reference and without creating addition variables.
fun Any.addObservable<T>(prop: KProperty<T>, observer: (T) -> Unit) {
getObservableProperty(prop).observers.add(observer)
}
fun Any.getObservableProperty<T>(prop: KProperty<T>): ObservableProperty<T> {
return getDelegate(prop) as ObservableProperty<T>
}
fun Any.getDelegate<T>(prop: KProperty<T>): Any {
return javaClass.getDeclaredField("${prop.name}\$delegate").let {
it.setAccessible(true)
it.get(this)
}
}
class ObservableProperty<T>(
initialValue: T,
initialObservers: Array<(T) -> Unit> = emptyArray()) : ReadWriteProperty<Any?, T> {
private var value = initialValue
public val observers: MutableSet<(T) -> Unit> = initialObservers.toHashSet()
public override fun get(thisRef: Any?, desc: PropertyMetadata): T {
return value
}
public override fun set(thisRef: Any?, desc: PropertyMetadata, value: T) {
this.value = value
observers.forEach { it(value) }
}
}
class A() {
var b by ObservableProperty(0)
}
fun main(args: Array<String>) {
val a = A()
a.addObservable(A::b) {
println("b is now $it")
}
a.b = 1
a.b = 2
a.b = 3
}
Edit:
I just realized that the function also isn't strict because the property delegate field name is referenced by KProperty name, which doesn't require a strong reference to the enclosing class. Here's an example to demonstrate the problem:
class A() {
var foo by ObservableProperty(0)
}
class B() {
var foo by ObservableProperty(0)
}
fun main(args: Array<String>) {
val a = A()
a.addObservable(B::foo) {
println("b is now $it")
}
a.foo = 1
a.foo = 2
a.foo = 3
}
This compiles and runs without error because A::foo and B::foo both result in a field string of "foo$delegate.
Right now reflection is all we can do to get to the delegate object. We are designing a language feature to have direct access to delegate instance, but it's long way to go.
This is how you get the name of a Kotlin Property (although only with an instance of the class). This part will be useful to anyone arriving at this question purely based off its title.
class Stuff(val thing: String)
val stuff = Stuff("cool stuff")
val thingFieldName = "${stuff.thing}\$delegate"
// value of thingFieldName is now "thing"
In terms of getting the delegate itself easier, they say you can now do this:
class Foo {
var bar: String by ReactiveProperty<String>()
}
val foo = Foo()
val bar = foo.bar
val barDelegate = ... // foo.bar$delegate
See ticket.