智能投射到“DrawerLayout!”是不可能的,因为 'drawerLayout' 是一个可变属性,此时可能已更改
Posted
技术标签:
【中文标题】智能投射到“DrawerLayout!”是不可能的,因为 \'drawerLayout\' 是一个可变属性,此时可能已更改【英文标题】:Smart cast to 'DrawerLayout!' is impossible, because 'drawerLayout' is a mutable property that could have been changed by this time智能投射到“DrawerLayout!”是不可能的,因为 'drawerLayout' 是一个可变属性,此时可能已更改 【发布时间】:2021-10-18 00:40:02 【问题描述】:我是一个完全的 android 开发新手,过去两天我一直被这个问题困扰,我的生活中从未感到如此沮丧。先说一点背景故事,
我正在创建最基本的图书库应用程序,并尝试向应用程序添加导航抽屉。在 kotlin 文件中,当我使用 lateinit 声明所有变量(对应于布局文件中创建的标签)时,它会抛出一个 nullPointerException。出于这个原因,我开始像这样声明我的变量:
var drawerLayout: DrawerLayout? = null
这有助于我避免异常。 现在来解决真正的问题,
我试图以这种方式在 onCreate 方法中为我的 actionBarDrawerToggle 创建一个 click 监听器:
val actionBarDrawerToggle = ActionBarDrawerToggle(this@MainActivity, drawerLayout, R.string.open_drawer, R.string.close_drawer)
drawerLayout.addDrawerListener(actionBarDrawerToggle)
actionBarDrawerToggle.syncState()
由于某种原因,“drawerLayout.addDrawerListener(actionBarDrawerToggle)”行的 drawerLayout 部分带有红色下划线,表示存在错误。当我运行它时,这是它在构建窗口中显示的错误:
Smart cast to 'DrawerLayout!' is impossible, because 'drawerLayout' is a mutable property that could have been changed by this time
我不知道如何进行此操作。我已经尝试了很多东西,但它们都不起作用。我认为该错误可能与我上面描述的我使用的声明方法有关。如果有人可以帮助我,那就太好了
【问题讨论】:
您有没有问过为什么在使用lateinit
时得到NullPointerException
的问题?也许您应该尝试解决您的核心问题,而不是尝试使用var
来解决它。
@ianhanniballake 那么我到底应该怎么做才能解决这个问题?我的视图 ID 和我的变量名都匹配,所以没有错误,我尝试过这样使用非 null 断言调用: var drawLayout: DrawerLayout!!这也不好用,我已经清理了项目,使缓存失效,我已经搜索了很多地方,但我似乎无法找到解决方案
【参考方案1】:
从战术上讲,将该行更改为:
drawerLayout?.addDrawerListener(actionBarDrawerToggle)
?.
在 Kotlin 中是“安全调用”运算符。它说:
如果drawerLayout
不是null
,请致电addDrawerListener()
如果drawerLayout
是 null
,什么也不做(从技术上讲,它的计算结果为null
,但你在这里没有使用它)
这将允许您编译。代码是否工作将取决于您在到达该行时是否已成功在drawerLayout
上设置了非null
值。您的lateinit var
声明失败这一事实表明您没有填充drawerLayout
— 使用lateinit var
,您会在运行时崩溃;使用可空属性和安全调用,该行将被忽略。两者都不是你想要的。
理想情况下,您正在阅读的有关 Kotlin 和 Android 应用程序开发的书籍(或您正在学习的课程)将涵盖诸如安全调用、如何避免拥有像 drawerLayout
这样的属性等内容。如果您没有阅读有关 Kotlin 和 Android 应用程序开发的书籍或课程,那么您可能应该考虑这样做。
【讨论】:
感谢您的建议!但我已经尝试过了。应用程序再次崩溃,这是它在 logcat 窗口中引发的错误:java.lang.NullPointerException: Attempt to invoke virtual method 'boolean androidx.drawerlayout.widget.DrawerLayout.isDrawerOpen(int)' on a null object reference @jas:那是在你的代码后面的某个地方。正如我所提到的,在尝试使用它之前,您需要为drawerLayout
分配一个值。从lateinit var
到可空属性的所有切换都会改变您在使用之前未将值分配给drawerLayout
时遇到的故障的性质。这种错误是当前 Android 开发避免使用 drawerLayout
之类的属性(使用视图绑定)以及下一代 Android 开发完全摆脱布局资源(通过 Jetpack Compose)的原因。【参考方案2】:
您已将 drawerLayout
定义为可空 (DrawerLayout?
) var
,这意味着该值可以随时更改,并且该值可以为空。因此,即使您进行空检查以确保它不为空,也可能随时改变!编译器无法保证调用是安全的。
这里有一些选项,从坏到好:
通过引用drawerLayout!!
将变量显式标记为非空。这是你告诉编译器“我知道发生了什么,这很好,相信我”。你应该避免这样做,因为这是一个危险信号,你会忽略警告,你最终会遇到错误的情况
将非空值复制到一个临时变量中,你知道它不会改变。这是在 Kotlin 中处理可空值的典型方法,您可以使用 let
之类的作用域函数之一:
drawerLayout?.let drawer -> // do stuff with drawer
这是一个空值检查(使用?
)并且如果它不是空值,它使用该非空值调用let
块。而且您知道该值在块内不会改变。我已经调用了变量drawer
,但你可以省略它,默认情况下它被称为it
。还有像run
这样的其他作用域函数,做同样事情的方式略有不同。
你也可以只调用被检查的变量本身,如果这就是你需要做的:
drawerLayout?.doAThingItsNotNullSoItsFine()
这基本上是在做同样的事情
首先不要让drawerLayout
可以为空!它实际上永远不应该为空,对吧?它应该始终存在,并且在您尝试访问它时具有价值?然后为自己省去仇恨和一直对它进行空检查的需要 - 这就是 lateinit
的用途。
lateinit
允许你声明一个变量而不实际给它一个值。通常你必须这样做,这就是你在这里所做的正确 - 你已经使它可以为空,这样你就可以在那里粘贴一个临时的空值。 lateinit
是对编译器的承诺,它没关系,你还没有提供一个值,但是你肯定会在任何尝试访问它之前设置一个。 (这就是为什么它被称为后期初始化!)
因此,您遇到的问题是您在尝试读取该值之前实际上并未设置该值。您可以检查它是否已使用::drawerLayout.isInitialized
进行设置,但实际上您应该充分了解您的代码流,以确保只有在初始化后才能读取。如果您不能保证这一点,那么您可能会遇到一种可能的状态,即您没有拥有其他东西可能需要访问的变量的值 - 这是使其可空的一个很好的候选者,并且使用 null 处理特性来顺利处理它
【讨论】:
感谢您的建议!关于您提到的第三种方法,我尝试使用 lateinit 声明变量,但应用程序崩溃并抛出 nullPointerExeption,即使在 onCreate 方法中我已将变量初始化为:drawerLayout = findViewById(R.id.drawerLayout) .错误说 findViewById(R.id.drawerLayout) 不能为空 这意味着它没有在布局中找到视图 - 检查 ID 是否正确(布局中的 ID 需要匹配)并且您在setContentView
之后调用 findViewById
调用(因此它具有可搜索的膨胀布局)以上是关于智能投射到“DrawerLayout!”是不可能的,因为 'drawerLayout' 是一个可变属性,此时可能已更改的主要内容,如果未能解决你的问题,请参考以下文章
一个应用程序是不是可以有多个 CastOptionsProvider,作为投射到 2 个不同的接收器应用程序的一种方式?
如何从 Android 应用程序中发现 Roku 设备以进行投射
带有谷歌电视的 Chromecast 是不是需要 Android TV 接收器应用程序才能进行投射?