0

The app I'm developing has three classes. MainActivity (not important here), MyFragment and MyClass. I have a lateinit List inside MyFragment - it is lateinit because it will be used by some methods. (I'm using kotlin synthetics by the way)

class MyFragment: Fragment()
    lateinit var myViewsList: List<View>
    lateinit var viewA: View
    lateinit var viewB: View
    lateinit var viewC: View

    override fun onCreateView(
    //inflating the layout...
    ): View? {
        val view = inflater.inflate(R.layout.fragment_timer, container, false)

        val button: Button = view.myButton
        viewA = view.myViewA
        viewB = view.myViewB
        viewC = view.myViewC

        myViewsList =
            listOf<View>(viewA,viewB,viewC) 

        myButton.setOnClickListener() {
            MyClass().myMethod()
        }

        return view
    }

And then MyClass:

class MyClass() {
    fun showViews {
        MyFragment().myViewsList.forEach { // this line is causing the error
            it.visibility = View.VISIBLE
        }
    }

    fun myMethod() {
        //do some stuff
        //delay...
        showViews()
    }


}

But I get the "lateinit property myViewsList not initialized" error whenever showViews() is called (and it isn't called when the fragment is destroyed, started or something like that. In my app, it'd take you ~10 seconds to call it - I've just put it in a OnClickListener for simplicity - and there aren't any activity changes during those 10 seconds).

I don't understand why, as it is initialized in OnCreateView myViewsList = listOf<View>(viewA,viewB,viewC). Any calls involving myViewsList inside MyFragment work just fine, but not inside MyClass. So, why is this happening? and what would be the correct way of calling or initializing it?

  • 1
    Check this post, it would solve your problem : https://stackoverflow.com/a/46584412/9636037 – Abhimanyu May 30 '20 at 17:19
  • 1
    `MyFragment().myViewsList` you're creating new fragment instance instead of passing reference to the existing one. – Pawel May 30 '20 at 17:38
  • @Abhimanyu Hmm... wouldn't it always just skip past the part with `if (...isInitialized)` in my code - as it says "... has not been initialized"? I'll try that anyways. Thanks – relative_lay0ut May 30 '20 at 18:19
  • First, what is the relation between two? Is the fragment part of MyClass's viewPager or is loaded in MyClass's activity? Or MyClass is started as a new activity? this matters for the answer. Because if it's a part of ViewPager or FragmentAdapter, you can get a reference of the fragment through it but if it's a new activity, use OnResult() in the fragment and modify the list as the returned values from the class. So, what is the connection? – Lalit Fauzdar May 30 '20 at 18:20
  • The `if (...isInitialized)` will only stops you from getting the error, just as a null check. It won't solve your issue, it just skips the issue. – Lalit Fauzdar May 30 '20 at 18:22
  • @Pawel That makes sense. But how do I pass a reference of the existing one without involving my fragment? because I can't do `view.findViewById...` or in this case use the synthetic `viewA = myViewA` inside `MyClass` without also "calling" my fragment. Maybe that's because only `myFragment` has a "relation" to the layout? – relative_lay0ut May 30 '20 at 18:23
  • @LalitFauzdar I'm not using viewPagers here, just a List, sorry if my question was confusing. I'll try to explain: `myFragment` has onClickListeners and some methods in it. It is a part of a NavigationDrawer (there will be other fragments too). myFragment has a onClickListener that calls a method from `myClass` that hides the views. I'm using a `List` here because if I need to show/hide any more views it wouldn't be very good to type multiple times `viewA.visibility = View.INVISIBLE"` ... until, say, `viewF.visibility = View.INVISIBLE`, so using a List is way more efficient IMO. – relative_lay0ut May 30 '20 at 18:30
  • @LalitFauzdar Basically a `NavigationDrawer` has `myFragment`, `myFragment` calls a method from `myClass`. When I call the method from `myClass`, it crashes saying the List (that I initialized inside onCreateView) is uninitialized. And about the null check, I wouldn't say it's a good idea to use it if hiding the views is an essential part of my app - which would be always skipped as the List is "uninitialized". – relative_lay0ut May 30 '20 at 18:33
  • 1
    I meant to say that it will act like a null check but it won't solve your problem, it didn't mean that you should not use null check, they're very important. Now, because you're using `MyFragment` in `NavigationDrawer`, what you can do is you can pass it's reference so when you'll call the list in `MyClass`, you'll call the actual list which is already initialized. What you do here is you don't actually pass the reference, you create a new reference of that fragment which is obviously the reason of uninitialized list. – Lalit Fauzdar May 30 '20 at 18:44
  • 1
    I found a similar [answer](https://stackoverflow.com/a/59967247/8244632) but it's not for NavigationDrawer but it can help. You can use a similar reference in your case. – Lalit Fauzdar May 30 '20 at 18:46
  • Henrique Vasconcellos' answer (calling `myClass` and passing the List as a parameter) worked, but thank you anyways @LalitFauzdar! – relative_lay0ut May 30 '20 at 19:18

1 Answers1

1

I may be wrong here, but I dont think an Android fragment class will work as any other regular class. When you are invoking MyFragment().myViewsList you are not calling the same instance of your fragment and as you are calling this variable "myViewsList" that hasnt been initialized, it crashes.

To make it work, you will have to pass your list and the view that would you like to make it visible.

class MyFragment: Fragment()
    lateinit var myViewsList: List<View>
    lateinit var viewA: View
    lateinit var viewB: View
    lateinit var viewC: View

    override fun onCreateView(
    //inflating the layout...
    ): View? {
        val view = inflater.inflate(R.layout.fragment_timer, container, false)

        val button: Button = view.myButton
        viewA = view.myViewA
        viewB = view.myViewB
        viewC = view.myViewC

        myViewsList =
            listOf<View>(viewA,viewB,viewC) 

        myButton.setOnClickListener() {
            MyClass(myViewsList).myMethod()
        }

        return view
    }

and your class should be like this:

class MyClass(private val views: List<View>) {
    fun showViews {
        views.forEach { // this line is causing the error
            it.visibility = View.VISIBLE
        }
    }

    fun myMethod() {
        //do some stuff
        //delay...
        showViews()
    }


}
Henrique Vasconcellos
  • 1,144
  • 1
  • 8
  • 13
  • So I need to create this List (therefore its views too) again inside `MyClass`, right? And I have a question, why hasn't `myViewList` been initialized if I initialized it (inside onCreateView)? or is it just initialized "locally" in onCreateView? And if that's the case, then why do methods involving the List work inside `MyFragment` (but outside of onCreateView)? if it is local, I guess it should work only _inside onCreateView_, not outside. Maybe I'm missing something here. – relative_lay0ut May 30 '20 at 18:14
  • You dont need to recreate, I will update the answer with a sample of what you can do. It was initialized, if you try to manipulate it inside the fragment, like creating a fun that refers to it, it will work. But what you were trying to do was creating a new MyFragment that wanst initialized, the fragment lifecycle will start when you attach it, so when you call like you did "MyFragment.list" you skipped this part hence the crash. – Henrique Vasconcellos May 30 '20 at 18:40
  • I can't create a List and its views without involving `myFragment`. By initializing the views as `viewA = myFragment().myViewA`, `viewB = ... myViewB` it crashes with the following error _"java.lang.IllegalStateException: myFragment().myViewA must not be null"_. – relative_lay0ut May 30 '20 at 18:41
  • Thank you. I'll wait for your sample and try to implement it into my code. – relative_lay0ut May 30 '20 at 18:44
  • It now works, thank you! Just one more question. If I ever call `myClass` somewhere else to use another method of it (which will have no relation to hiding views, so there would be no need to pass any List to it), is there a way to maybe make a "no List parameter needed" version? like, just calling `myClass().someOtherMethod` instead of having to pass a (in this case, unnecessary) List? I've thought about instead of having to pass a List everytime it is called, just make a `setList()` method that receives a List once and call it on `myFragment`. I'll try that and see if it works. – relative_lay0ut May 30 '20 at 19:12
  • yes, you can construct ur MyClass like this: "MyClass(private val views: List? = null)" .. so if you dont pass any value it will be null. – Henrique Vasconcellos May 30 '20 at 19:20
  • to make it like you want, you can use objects, like this: https://kotlinlang.org/docs/tutorials/kotlin-for-py/objects-and-companion-objects.html – Henrique Vasconcellos May 30 '20 at 19:25