智能投射到“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 接收器应用程序才能进行投射?

使用 Android 智能手机的 Wi-Fi 热点进行 Chrome 投射[关闭]

我是不是需要注册 Android TV 才能接收投射?