Home > Back-end >  Functions which invoke @Composable functions must be marked with the @Composable annotation in WebVi
Functions which invoke @Composable functions must be marked with the @Composable annotation in WebVi

Time:01-05

I have a custom Compose Function for WebView, in Which I want to show the Custom progress bar while loading the page but it gives this error.

Functions which invoke @Composable functions must be marked with the @Composable annotation

This is my Composable.

package com.example.devdocs

import android.graphics.Bitmap
import android.view.ViewGroup
import android.webkit.WebChromeClient
import android.webkit.WebView
import android.webkit.WebViewClient
import androidx.activity.compose.BackHandler
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.viewinterop.AndroidView

@Composable
fun webView(
    url: String = "www.google.com"
) {
    var backEnabled by remember { mutableStateOf(false) }
    var webView: WebView? = null
    AndroidView(
        factory = { context ->
            WebView(context).apply {
                settings.javaScriptEnabled = true
                settings.userAgentString
                settings.domStorageEnabled
                settings.setSupportZoom(true)
                settings.builtInZoomControls = true
                settings.displayZoomControls = false
                layoutParams = ViewGroup.LayoutParams(
                    ViewGroup.LayoutParams.MATCH_PARENT,
                    ViewGroup.LayoutParams.MATCH_PARENT
                )
                webViewClient = object : WebViewClient() {
                    override fun onPageStarted(view: WebView, url: String?, favicon: Bitmap?) {
                        backEnabled = view.canGoBack()
                        LoadingAnimation(
                            modifier = Modifier
                                .fillMaxWidth(),
                            isVisible = true
                        )
                    }

                    override fun onPageFinished(view: WebView?, url: String?) {
                        super.onPageFinished(view, url)
                        LoadingAnimation(
                            modifier = Modifier
                                .fillMaxWidth(),
                            isVisible = false
                        )
                    }
                }
                webChromeClient = WebChromeClient()
                loadUrl(url)
                webView = this
            }

        }, update = {
            webView = it
        })
    BackHandler(enabled = backEnabled) {
        webView?.goBack()
    }
}

"LoadingAnimation" is a custom animation class that shows the animation. Here is Animation Class Source Code.

package com.example.devdocs

import androidx.compose.animation.core.*
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.delay

@Composable
fun LoadingAnimation(
    modifier: Modifier = Modifier,
    circleSize: Dp = 25.dp,
    circleColor: Color = MaterialTheme.colorScheme.primary,
    spaceBetween: Dp = 10.dp,
    travelDistance: Dp = 20.dp,
    isVisible: Boolean = true
) {
    if (isVisible) {
        val circles = listOf(
            remember { Animatable(initialValue = 0f) },
            remember { Animatable(initialValue = 0f) },
            remember { Animatable(initialValue = 0f) }
        )

        circles.forEachIndexed { index, animatable ->
            LaunchedEffect(key1 = animatable) {
                delay(index * 100L)
                animatable.animateTo(
                    targetValue = 1f,
                    animationSpec = infiniteRepeatable(
                        animation = keyframes {
                            durationMillis = 1200
                            0.0f at 0 with LinearOutSlowInEasing
                            1.0f at 300 with LinearOutSlowInEasing
                            0.0f at 600 with LinearOutSlowInEasing
                            0.0f at 1200 with LinearOutSlowInEasing
                        },
                        repeatMode = RepeatMode.Restart
                    )
                )
            }
        }

        val circleValues = circles.map { it.value }
        val distance = with(LocalDensity.current) { travelDistance.toPx() }

        Row(
            modifier = modifier,
            horizontalArrangement = Arrangement.spacedBy(spaceBetween)
        ) {
            circleValues.forEach { value ->
                Box(
                    modifier = Modifier
                        .size(circleSize)
                        .graphicsLayer {
                            translationY = -value * distance
                        }
                        .background(
                            color = circleColor,
                            shape = CircleShape
                        )
                )
            }
        }
    }
}

How can I solve this? Thanks.

CodePudding user response:

The Error @Composable functions can only be called from other @Composable functions, says that COMPOSABLE FUNCTIONS can be called only from another Composable context.

In your code 'LoadingAnimation' is a composable function, which is called from a NON- Composable context

You needed to show/hide a composable UI once a page starts/finishes. The best way to achieve this is using a MutableState Object with remember function

Kindly check the below refactored code

@Composable
fun webView(
    url: String = "https://www.google.com/", callback: (isLoading: Boolean) -> Unit
) {
    var backEnabled by remember { mutableStateOf(false) }
    var webView: WebView? = null
    AndroidView(
        factory = { context ->
            WebView(context).apply {
                settings.javaScriptEnabled = true
                settings.userAgentString
                settings.domStorageEnabled
                settings.setSupportZoom(true)
                settings.builtInZoomControls = true
                settings.displayZoomControls = false
                layoutParams = ViewGroup.LayoutParams(
                    ViewGroup.LayoutParams.MATCH_PARENT,
                    ViewGroup.LayoutParams.MATCH_PARENT
                )
                webViewClient = object : WebViewClient() {
                    override fun onPageStarted(view: WebView, url: String?, favicon: Bitmap?) {
                        backEnabled = view.canGoBack()
                        callback(true)
                    }

                    override fun onPageFinished(view: WebView?, url: String?) {
                        super.onPageFinished(view, url)
                        callback(false)
                    }
                }
                webChromeClient = WebChromeClient()
                loadUrl(url)
                webView = this
            }

        }, update = {
            webView = it
        })
    BackHandler(enabled = backEnabled) {
        webView?.goBack()
    }
}

You need to place the custom progress bar on top of the webview, so there are several ways to do it, I am currently using box here.

@Composable
fun JetpackWebView() {
    var isLoading by remember {
        mutableStateOf(true)
    }

    Box(modifier = Modifier.fillMaxSize()) {
        webView(callback = { value -> isLoading = value })
        if (isLoading) {
            CustomLinearProgressBar()
        }
    }
}

Code for custom Progress Bar (You can use your own custom progress here)

@Composable
fun CustomLinearProgressBar(){
    Column(modifier = Modifier.fillMaxWidth()) {
        LinearProgressIndicator(
            modifier = Modifier
                .fillMaxWidth()
                .height(15.dp),
            backgroundColor = Color.LightGray,
            color = Color.Red
        )
    }
}
  • Related