I'm refactoring an activity that had grown too large. Ideally what I am trying to accomplish is to have my activity initialize all my view and set Listeners. Than off load the logic to a helper class. I pretty sure I would like to do this with an interface. But that's were I'm stuck.
For example, let have classes Main and MainHelper. Main has a CardView and a button. The button will show the cardview.
MainHelper is what has the interface and Main implements it?
How do I update views from MainHelper?
Is there a better approach to what I'm trying to accomplish?
class MainActivity : AppCompatActivity(), MainHelper.MainActivityHelper {
private lateinit var btn: Button = findViewById(R.id.btn)
private lateinit var menu: CardView = findViewById(R.id.menu)
override fun onCreate(savedInstanceState: Bundle?) {
btn.setOnClickListener { v: View? -> handleBtn()}
}
override fun handleBtn() {}
}
class MainHelper: AppCompatActivity() {
interface MainActivityHelper {
fun handleBtnM() {
menu.visibility = View.VISIBLE
}
}
Instead of using this Helper class, I would recommend that you use a more modern and better tested architecture pattern like Model-View-ModelView (MVVM). If so, you can leverage Android Jetpack's Architecture Component to help you better organize and separate concerns as explained here: https://developer.android.com/jetpack/guide.
Here's a more in-depth explanation of Android's ViewModel implementation: https://developer.android.com/topic/libraries/architecture/viewmodel.
Related
I am getting a random crash "lateinit property binding has not been initialized". Most of the time it's working fine but a few time randomly we are getting this crash on crashlytics.
Please let me know what's wrong here
I have a BaseActivity with following code
abstract class BaseActivity<D : ViewDataBinding> : AppComptActivity() {
abstract val layoutId: Int
lateinit val binding: D
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState:Bundle)
binding = DataBindingUtil.setContentView(this, layoutId)
....
}
}
I have a HomeActivity which override BaseActivity with following code
class HomeActivity : BaseActivity<ActivityHomeBinding>() {
override val layoutId: Int get() = R.layout.activity_home
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState:Bundle)
....
}
}
I am using bottomNavigation menu and one of the fragment is HomeFragment
class HomeFragment : BaseFragment<FragmenntHomeBinding>() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState:Bundle)
(activity as HomeActivity).binding.appBarHome.visible(false)
//HERE I AM GETTING lateinit property binding has not been initialized crash
}
}
I don't want to use isInitialized property of lateinit as this will not solve my issue
As mentioned in the comment, I'd suggest instead of calling parent container (Activity) objects directly, register a listener to a navigation change like this in HomeActivity:
navController.addOnDestinationChangedListener { controller, destination, arguments ->
if(destination.id = R.id.homeFragment) {
// TODO hide/show your view here
}
}
In that case, you are sure that the view gets hidden/shown when it should be without relying on the HomeFragment being only in HomeActivity as this can change in the future and your app will start crashing
If you have an orientation change or other config change, or the OS process is killed while in the background and the user returns to the app, Android will recreate the Activity and the Fragments.
Unfortunately, it creates the Fragments first, before creating the Activity. So you cannot rely on the existence of the Activity until the Fragment has been attached to the Activity. You should move code that relies on the existence of the Activity to
onActivityCreated().
Note: I also agree with the comment about not doing it this way. Your Fragment should not make assumptions like this (that it is hosted by HomeActivity), but instead should make some callback to the hosting Activity and let the hosting Activity set the visibility of the app bar (or whatever else it wants to do).
I am trying to achieve Fragment to Activity communication given that the Activity is not the parent Activity.
So, I have a MainActivity that has a fragment called ContactListFragment, while the add button on the BottomNavigationView of MainActivity is clicked, I am opening another AddContactActivity to add a contact. My requirement is to when I am clicking the save button on the AddContactActivity, I need to initiate a data-sync with server in ContactListFragment. I think the best approach would be to use a Shared View Model for this, but in that Case how should I create the view model so that the lifecycle owner doesn't get changed?
I thought about using the Application Context as the owner but I feel like its an overkill for a task like this, and it may produce some consequences down the line when other modules are added to the project.
So is there a way to efficiently implement this approach? Thanks.
Write an interface class with object/objects/Data types you need to sync
interface OnSaveClickListener {
fun onSaveClicked(contact: Contact)
}
Now in ContactListFragment class
class ContactListFragment : Fragment(), OnSaveClickListener {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
(activity as AddContactACtivity).mOnSaveClickListener = this
}
override fun onSaveClicked(contact: Contact) {
// Whatever you want to do with the data
}
}
In AddContactActivity,
class AddContactActivity {
var mOnSaveClickListener : OnSaveClickListener? = null
private void whenYouClickSave(contact: Contact){
mOnSaveClickListener?.onSaveClicked(contact)
}
I wrote a simple SwipeLayout library. My goal is to publish it via GitHub for everyone, including myself, to use.
I encountered a problem when I decided to encapsulate some methods. I was hoping for an internal modifier to kick in, and solve the problem, but I can't find a proper way to do it.
Here are some code and explanation:
class SwipeLayout (...) : FrameLayout(...),
BackgroundViewsVisibilityController, ... {
private val backgroundController = BackgroundController(
this //(BackgroundViewsVisibilityController)
)
override fun revealLeftUnderView() {...}
override fun hideLeftUnderView() {...}
override fun revealRightUnderView() {...}
override fun hideRightUnderView() {...}
}
interface BackgroundViewsVisibilityController {
fun revealLeftUnderView()
fun revealRightUnderView()
fun hideLeftUnderView()
fun hideRightUnderView()
}
These are the methods to hide from users. How can I best achieve it?
internal tells the compiler that this interface, class or function can only be called from the current module. When you release a library, that internal interface can not be called (without using reflection) from your library.
You want to make SwipeLayout public (for third party usage) so you can not make the interface BackgroundViewsVisibilityController internal at the same time, because your class is extending that interface.
If you really want that clients can not call the functions from your interface, consider not having an interface (as an interface is usually there for a caller to be used) but making those functions private in SwipeLayout:
class SwipeLayout (...) : FrameLayout(...)
private fun revealLeftUnderView() {...}
private fun hideLeftUnderView() {...}
private fun revealRightUnderView() {...}
private fun hideRightUnderView() {...}
}
More information about Kotlin Visibility Modifiers
I'm trying to update values in a TextView from an external class but not work, I'm trying with many ways y many posts but unlucky me... BTW sent the TextView like a parameter in class and works but we know that not is the best way if I have many views.
So first with a basic code:
In Main Activity XML:
<TextView
android:id="#+id/tvHello"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
In code:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
ClaseDePrueba(this#MainActivity)
}
}
Finally in class with my last proof
class ClaseDePrueba(ctx : Context)
{
val view = LayoutInflater.from(ctx).inflate(R.layout.activity_main,null,false)
view.tvHello.text = "New Value"
}
Even I try to use the Kotlin android extensions like this web site but not work for me
https://antonioleiva.com/kotlin-android-extensions/
import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.android.synthetic.main.activity_main.view.*
A few hours ago I tried to implement an interface but I don't know how to reference the TextView and chance value, my code:
class MainActivity : AppCompatActivity() , ClaseDePrueba.MyInterfaceClass {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
ClaseDePrueba(this#MainActivity)
}
override fun updateClass() {
TODO("Not yet implemented")
}
And my class
class ClaseDePrueba(ctx: Context)
{
interface MyInterfaceClass {
fun updateClass(
)
}
So the question is... What is the (best) way to fix and do it work correctly?
UPDATE and one solution
well I solved created a list of objects and pass as a parameter
I don't know if is the best way but at the moment works thanks all
class MainActivity : AppCompatActivity() , ClaseDePrueba.MyInterfaceClass {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val textViews = ArrayList<TextView>()
textViews.add(tvHello)
textViews.add(tvHello2)
ClaseDePrueba(this#MainActivity, textViews)
}
class ClaseDePrueba(ctx: Context, private val tviews: ArrayList<TextView>? = null)
{
init {
if (tviews != null) {
tviews[0].text = "Init1"
tviews[1].text = "Init2"
}
}
You can create an Interface and try to implement it in your activity.
interface MyInterface { fun update() }
Then try to access the function with the reference of class.
`Class MainActivity : AppCompatActivity() ,MyInterface{
override fun update(){
}
}'
Access this function with class reference and update the value.
If you have an Activity, and it inflates a layout, it creates the objects in the XML and assigns them IDs. The XML is like a recipe, the Activity is the cook, baking a delicious view layout.
So your Activity has some TextView objects. If you have another class, and that inflates the same XML file, it creates different objects. It's baking its own layout, and any changes you make to those View objects won't be seen in the Activity, because it has a completely different set of TextView instances (which happen to share ID values, because that's what the XML recipe says)
So if you want to mess with those TextViews in the Activity, you have three basic options:
have the Activity pass them to the other class, in a collection like a list. ("Here you are, here's the stuff you need to work with")
make them publicly accessible in the Activity, so you can pass the Activity instance to your other class, and it can poke them directly e.g. myActivity.coolTextView1.text = "wow!"
make some kind of interface on the Activity, e.g. fun updateText(id: Int, text: String) which the other class can call when it needs to update something. The Activity handles the details internally, like finding the relevant TextView object and updating it
either way, if you want to change those specific TextView instances displayed in your Activity, you need to access those instances somehow
Try doing,
class ClaseDePrueba(ctx : Context)
{
val txtView : TextView = (ctx as MainActivity).findViewById(R.id.tvHello) as TextView
txtView.setText("Hello")
}
(No need to inflate layout again)
The other way could be to implement an Interface
I'm trying out the new Android Architecture components and have run into a road block when trying to use the MVVM model for a custom view.
Essentially I have created a custom view to encapsulate a common UI and it's respective logic to use throughout the app. I can set up the ViewModel in the custom view but then I'd have to either use observeForever() or manually set a LifecycleOwner in the custom view like below but neither seem correct.
Option 1) Using observeForever()
Activity
class MyActivity : AppCompatActivity() {
lateinit var myCustomView : CustomView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
myCustomView = findViewById(R.id.custom_view)
myCustomView.onAttach()
}
override fun onStop() {
myCustomView.onDetach()
}
}
Custom View
class (context: Context, attrs: AttributeSet) : RelativeLayout(context,attrs){
private val viewModel = CustomViewModel()
fun onAttach() {
viewModel.state.observeForever{ myObserver }
}
fun onDetach() {
viewModel.state.removeObserver{ myObserver }
}
}
Option 2) Setting lifecycleOwner from Activity`
Activity
class MyActivity : AppCompatActivity() {
lateinit var myCustomView : CustomView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
myCustomView = findViewById(R.id.custom_view)
myCustomView.setLifeCycleOwner(this)
}
}
Custom View
class (context: Context, attrs: AttributeSet) : RelativeLayout(context,attrs){
private val viewModel = CustomViewModel()
fun setLifecycleOwner(lifecycleOwner: LifecycleOwner) {
viewModel.state.observe(lifecycleOwner)
}
}
Am I just misusing the patterns and components? I feel like there should be a cleaner way to compose complex views from multiple sub-views without tying them to the Activity/Fragment
1 Option -
With good intention, you still have to do some manual work - like, calling onAttach\ onDetach Main purpose of Architecture components is to prevent doing this.
2 Option -
In my opinion is better, but I would say it's a bit wrong to bind your logic around ViewModel and View. I believe you can do same logic inside Activity/Fragment without passing ViewModel and LifecycleOwner to CustomView. Single method updateData is enough for this purpose.
So, in this particular case, I would say it's overuse of Architecture Components.
it doesn't make sense to manage the lifecycle of the the view manually by passing some reference of the activity to the views and calling onAttach/onDetach, when we already have the context provided when that view is created.
I have a fragment in a NavigationView that has other fragments in a view pager, more like a nested fragment hierarchy scenario.
I have some custom views in these top-level fragments, when the custom view is directly in the top fragment, I can get an observer like this
viewModel.itemLiveData.observe((context as ContextWrapper).baseContext as LifecycleOwner,
binding.item.text = "some text from view model"
}
when I have the custom view as a direct child of an activity I set it up directly as
viewModel.itemLiveData.observe(context as LifecycleOwner,
binding.item.text = "some text from view model"
}
in these activities, if I have a fragment and it has some custom view and I use the 2nd approach, I get a ClassCastException(), and I have to reuse these custom views in different places, both activities, and fragments (that's the idea of having a custom view)
so i wrote an extension function to set the LifeCycleOwner
fun Context.getLifecycleOwner(): LifecycleOwner {
return try {
this as LifecycleOwner
} catch (exception: ClassCastException) {
(this as ContextWrapper).baseContext as LifecycleOwner
}
}
now i simply set it everywhere as
viewModel.itemLiveData.observe(context.getLifecycleOwner(),
binding.item.text = "some text from view model"
}