I would like to open the WebView from my callback. But I am getting the following error
我想从我的回调中打开WebView。但我收到了以下错误
@Composable invocations can only happen from the context of a @Composable function
@Composable调用只能从@Composable函数的上下文中进行
This code snippet is the issue
这个代码片段就是问题所在
onNewsLinkedClicked = { newsLink ->
WebViewScreen(webLink = newsLink)
}
I did try and declare my lambda in the NewsScreen function like this.
我确实尝试过在NewsScreen函数中声明我的lambda,如下所示。
onNewsLinkedClicked: @Composable (newsLink: String) -> Unit
OnNewsLinkedClicked:@Composable(新闻链接:字符串)->单位
That solved the issue below. But then I got another issue, as I launch the callback from a Button onClick event which started to give me an error.
这就解决了下面的问题。但后来我遇到了另一个问题,因为我从一个Button onClick事件启动回调,它开始给我一个错误。
Is there any way of doing this for my code below?
对于我下面的代码,有什么方法可以做到这一点吗?
@AndroidEntryPoint
class MainActivity : ComponentActivity() {
@OptIn(ExperimentalMaterial3Api::class)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
val newsViewModel: NewsViewModel = hiltViewModel()
val newsHeadLines = newsViewModel.newsPager.collectAsLazyPagingItems()
BeerPagingTheme {
// A surface container using the 'background' color from the theme
val scrollBehavior = enterAlwaysScrollBehavior()
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
NewsScreen(
newsPagingData = newsHeadLines,
modifier = Modifier
.fillMaxSize()
.nestedScroll(connection = scrollBehavior.nestedScrollConnection),
topAppBarScrollBehavior = scrollBehavior,
onNewsLinkedClicked = { newsLink ->
WebViewScreen(webLink = newsLink)
}
)
}
}
}
}
}
@Composable
fun WebViewScreen(webLink: String) {
AndroidView(
factory = { context ->
WebView(context).apply {
this.layoutParams = ViewGroup.LayoutParams(
LayoutParams.MATCH_PARENT,
LayoutParams.MATCH_PARENT)
loadUrl(webLink)
} },
update = { webView ->
webView.loadUrl(webLink)
},
modifier = Modifier.fillMaxSize())
}
更多回答
You probably want to look at a navigation solution, whether you use the official Navigation for Compose or Appyx or compose-destinations or Voyager or any of the couple of dozen other third-party navigation libraries. Alternatively, your button should mutate some state that causes you to show WebViewScreen()
instead of NewsScreen()
.
您可能想看看导航解决方案,无论是使用Compose、Appyx、Compose-Destination、Voyager的官方导航,还是其他几十个第三方导航库中的任何一个。或者,您的按钮应该改变某种状态,使您显示WebViewScreen()而不是NewsScreen()。
@CommonsWare I am not sure what you mean by this Alternatively, your button should mutate some state that causes you to show WebViewScreen() instead of NewsScreen()
Can you give an example?
@CommonsWare我不确定这是什么意思。或者,您的按钮应该改变某种状态,使您显示WebViewScreen()而不是NewsScreen()。您能举个例子吗?
优秀答案推荐
You can use navigation solution.
Alternatively, your button should mutate some state that causes you to show WebViewScreen()
instead of NewsScreen()
as below:
您可以使用导航解决方案。或者,您的按钮应该改变某种状态,使您显示WebViewScreen()而不是NewsScreen(),如下所示:
var clickedLink by rememberSavable { mutableStateOf("") }// state for saving clicked link
if (clickedLink.isEmpty) {
NewsScreen(
newsPagingData = newsHeadLines,
modifier = Modifier
.fillMaxSize()
.nestedScroll(connection = scrollBehavior.nestedScrollConnection),
topAppBarScrollBehavior = scrollBehavior,
onNewsLinkedClicked = { newsLink ->
clickedLink = newsLink // changing clicked link address
}
)
} else {
WebViewScreen(webLink = clickedLink)
}
For scalability, I recommend using one of the many navigation solutions available for Compose UI. That could be Navigation for Compose, if you want a purely official solution. If you want a wrapper over that, consider compose-destinations. If you want something fully independent, there are Appyx and Voyager and many others.
为了提高可伸缩性,我推荐使用Compose UI可用的众多导航解决方案中的一个。如果你想要一个纯粹的官方解决方案,那可以是合成的导航。如果您想要一个封装器,可以考虑组合目的地。如果你想要完全独立的东西,有Appyx和Voyager和许多其他的。
Tactically, you could do something like:
从战术上讲,您可以这样做:
BeerPagingTheme {
// A surface container using the 'background' color from the theme
val scrollBehavior = enterAlwaysScrollBehavior()
var newsLink by remember { mutableStateOf<String?>(null) }
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
if (newsLink == null) {
NewsScreen(
newsPagingData = newsHeadLines,
modifier = Modifier
.fillMaxSize()
.nestedScroll(connection = scrollBehavior.nestedScrollConnection),
topAppBarScrollBehavior = scrollBehavior,
onNewsLinkedClicked = { newsLink = it }
)
} else {
WebViewScreen(webLink = newsLink)
}
}
}
(note: I'm doing this from memory, so there may need to be some adjustments)
(注:我是凭记忆完成这项工作的,因此可能需要进行一些调整)
Compose is a declarative reactive framework. You declare "this is what I want the UI to be, based upon the current state". In this case, the current state is newsLink
. You react to changes in that state. So, your onClick
lambda updates the state (newsLink = it
), causing Compose to re-run all of setContent()
and update the UI based upon a fresh declaration.
Compose是一个声明性反应性框架。在本例中,当前状态为新闻链接。你会对这种状态的变化做出反应。因此,onClick lambda更新状态(新闻链接=it),导致Compose重新运行所有setContent()并基于新的声明更新UI。
更多回答
我是一名优秀的程序员,十分优秀!