I am trying to run a test using Espresso in jetpack compose. Everytime I run it, it gives this error.
androidx.test.espresso.NoMatchingViewException: No views in hierarchy found matching: an instance of android.widget.TextView and view.getText() with or without transformation to match: is "Count"
My MainActivity.kt
package com.example.testing
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.material.Button
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.example.testing.ui.theme.TestingTheme
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
TestingTheme {
// A surface container using the 'background' color from the theme
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colors.background
) {
Greeting()
}
}
}
}
}
#Composable
fun Greeting() {
var counter by remember{
mutableStateOf(0)
}
Column(
modifier = Modifier
.fillMaxSize()
.wrapContentSize(Alignment.Center)
) {
Text(text = stringResource(id = R.string.Count),
modifier = Modifier
.padding(8.dp)
.testTag(stringResource(id = R.string.)))
Button(onClick = { counter++ }) {
Text(text = stringResource(id = R.string.Increment))
}
}
}
#Preview(showBackground = true)
#Composable
fun DefaultPreview() {
TestingTheme {
Greeting()
}
}
My test.kt
package com.example.testing
import androidx.compose.ui.test.junit4.createAndroidComposeRule
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import com.microsoft.appcenter.espresso.ReportHelper;
import androidx.lifecycle.Lifecycle
import androidx.test.core.app.ActivityScenario
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.*
import androidx.test.ext.junit.rules.ActivityScenarioRule
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import com.microsoft.appcenter.espresso.Factory
import org.junit.After
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.Assert.*
import org.junit.Before
import org.junit.Rule
/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
#RunWith(AndroidJUnit4::class)
#LargeTest
class ExampleInstrumentedTest {
#Rule
val activityRule = ActivityScenarioRule(MainActivity::class.java) //espresso
#Test
fun testClick() {
onView(withText("Count")).check(matches(isDisplayed()))
}
}
I have Count in string.xml as Count. I have emulator up and running fine. It seems like it is not able to detect the Activity.
In #Test, if I skip
.check(matches(isDisplayed()))
part from
onView(withText("Count")).check(matches(isDisplayed()))
it passes.
Also I have tried junit4
val composeTestRule = createAndroidComposeRule<MainActivity>()
And it works but espresso isn't working
You can use:
#get:Rule
val composeRule = createComposeRule()
#Test
fun testClick() {
composeRule.setContent {
TestingTheme {
Greeting()
}
}
composeRule.onNodeWithText("Count").assertIsDisplayed()
}
As alternative you can also use:
#OptIn(ExperimentalFoundationApi::class, ExperimentalComposeUiApi::class)
#get:Rule
val composeTestRule = createAndroidComposeRule<MainActivity>()
#OptIn(ExperimentalFoundationApi::class, ExperimentalComposeUiApi::class)
#Test
fun testClick() {
composeTestRule.onNodeWithText("Count").assertIsDisplayed()
}
Related
I want to disable black shadow when we disable Ripple effect on user click action. I tried from this answer 1 and answer 2. I made a github project to see the code.
MainActivity.kt
package com.example.disableshadow
import android.os.Bundle
import android.util.Log
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material.ripple.LocalRippleTheme
import androidx.compose.material.ripple.RippleAlpha
import androidx.compose.material.ripple.RippleTheme
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.LocalMinimumTouchTargetEnforcement
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.Preview
import com.example.disableshadow.ui.theme.DisableShadowTheme
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
DisableShadowTheme {
// A surface container using the 'background' color from the theme
Column(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Greeting()
}
}
}
}
}
#OptIn(ExperimentalMaterial3Api::class)
#Composable
fun Greeting() {
val interactionSource = remember { MutableInteractionSource() }
CompositionLocalProvider(
LocalMinimumTouchTargetEnforcement provides false,
LocalRippleTheme provides CustomRippleTheme(Color.Unspecified),
content = {
Surface(
onClick = { Log.e("ItemDisablePreview", "ItemClicked") },
interactionSource = interactionSource,
color = Color.White,
) {
Text(text = "Item Name")
}
}
)
}
#Preview(showBackground = true)
#Composable
fun DefaultPreview() {
DisableShadowTheme {
Greeting()
}
}
class CustomRippleTheme(private val rippleColor: Color) : RippleTheme {
#Composable
override fun defaultColor(): Color {
return if (rippleColor == Color.Unspecified) {
Color.Unspecified
} else {
RippleTheme.defaultRippleColor(
contentColor = rippleColor,
lightTheme = true
)
}
}
#Composable
override fun rippleAlpha() = when (rippleColor) {
Color.Red -> {
RippleAlpha(1f, 1f, 1f, 1f)
}
Color.Unspecified -> {
RippleAlpha(0.0f, 0.0f, 0.0f, 0.0f)
}
else -> {
RippleTheme.defaultRippleAlpha(
contentColor = rippleColor,
lightTheme = true
)
}
}
}
Image when we press.
I am in need of a datepicker in jetpack compose. Though to use the ordinary Datepicker in jetpack compose you need to parse Mainactivity to A AppcompatActivity. Though to do that what i understand the Mainactivity needs to extend AppCompatActivity? i am not able to get this to work can anyone please tell me what im doing wrong. I have already tried to extend AppCompatActivity as i said but then it gives you an error because it has to extend ComponentActivity? if i use this answer: AppCompatActivity instead of ComponentActivity in Jetpack compose i get this error.
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.jens.svensson.paper_seller/com.jens.svensson.paper_seller.MainActivity}: java.lang.IllegalStateException: You need to use a Theme.AppCompat theme (or descendant) with this activity.
Main Activity
package com.jens.svensson.paper_seller
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.material.rememberScaffoldState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController
import com.google.android.material.datepicker.MaterialDatePicker
import com.jens.svensson.paper_seller.core.navigation.Navigation
import com.jens.svensson.paper_seller.core.navigation.Screens
import com.jens.svensson.paper_seller.core.presentation.components.StandardScaffold
import com.jens.svensson.paper_seller.ui.theme.PaperSellerTheme
import dagger.hilt.android.AndroidEntryPoint
#AndroidEntryPoint
#ExperimentalComposeUiApi
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
PaperSellerTheme {
// A surface container using the 'background' color from the theme
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colors.background
) {
val navController = rememberNavController()
val navBackStackEntry by navController.currentBackStackEntryAsState()
val scaffoldState = rememberScaffoldState()
StandardScaffold(
modifier = Modifier.fillMaxSize(),
navController = navController,
navBackStackEntry = navBackStackEntry,
showBottomBar = navBackStackEntry?.destination?.route in listOf(
Screens.CustomerListScreen.route,
Screens.CalculationScreen.route,
Screens.ChatScreen.route,
Screens.SettingsScreen.route
),
showFloatButton = navBackStackEntry?.destination?.route in listOf(
Screens.CustomerListScreen.route
),
state = scaffoldState,
onFabClick = {navController.navigate(Screens.AddEditCustomerScreen.route)}
) {
Navigation(navController, scaffoldState)
}
}
}
}
}
}
#Preview(showBackground = true)
#Composable
fun DefaultPreview() {
PaperSellerTheme {
}
}
The datebuttonForm
package com.jens.svensson.paper_seller.feature_customer.presentation.components
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Icon
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.DateRange
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.core.util.Pair
import com.google.android.material.datepicker.MaterialDatePicker
import com.jens.svensson.paper_seller.core.util.Constants
#Composable
fun ChosenDateForm(datePicked: String?, updatedDate: (Pair<Long, Long>) -> Unit){
val activity = LocalContext.current as AppCompatActivity
Box(modifier = Modifier.clickable { showDatePicker(activity = activity, updatedDate = updatedDate)}
.padding(Constants.STANDARD_TEXTFIELD_PADDING.dp, top = 10.dp)
.background(MaterialTheme.colors.secondaryVariant, RoundedCornerShape(20))
.fillMaxWidth()
.border(width = 1.dp, color = MaterialTheme.colors.primary, RoundedCornerShape(20))){
Row(modifier = Modifier
.padding(16.dp)
.fillMaxWidth()) {
Text(modifier = Modifier.weight(1f), text = "Today", style = MaterialTheme.typography.body1)
Icon(Icons.Default.DateRange, contentDescription = "Select Date")
}
}
}
private fun showDatePicker(activity: AppCompatActivity, updatedDate: (Pair<Long, Long>) -> Unit){
val calendar = MaterialDatePicker.Builder.dateRangePicker().build()
calendar.show(activity.supportFragmentManager, calendar.toString())
calendar.addOnPositiveButtonClickListener {
updatedDate(it)
}
}
i'm practising a bit with kotlin and was testing Room and livedata, my app gets data from a json and the stores it in room, i want to move this network call to its own file and class, but if i do so the observer i set to get the changes don't trigger anymore, any help would be appreciated
here is a snipped of my mainactivity, if more is needed to know what happens please let me know
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.TextView
import android.widget.Toast
import androidx.activity.viewModels
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.viewModelScope
import androidx.room.Room
import com.optiva.videoplayer.data.*
import com.optiva.videoplayer.network.GetData
import com.optiva.videoplayer.network.Networking
import com.optiva.videoplayer.network.RetrofitConnect
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val dbcategories = Room.databaseBuilder(applicationContext, CategoriesDatabase::class.java,"categories.db").build()
val dbvideo = Room.databaseBuilder(applicationContext, VideosDatabase::class.java,"videos.db").build()
val retrofitData = RetrofitConnect.retrofitInst?.create(GetData::class.java)
val categoriesList = retrofitData?.getAll()
categoriesList?.enqueue(object: Callback<DataList> {
override fun onResponse(
call: Call<DataList>,
response: Response<DataList>
) {
val test = response?.body()
val cat = test?.categories
val vid = test?.videos
lifecycleScope.launch(Dispatchers.IO) {
if (cat != null) {
for(c in cat){
dbcategories.categoriesDAO().insertAll(CategoriesEntity(c.id,c.title,c.type))
}
}
if (vid != null) {
for(v in vid){
dbvideo.VideosDAO().insertAll(VideosEntity(v.id,v.thumb,v.videoUrl,v.categoryId,v.name))
}
}
}
}
override fun onFailure(call: Call<DataList>, t: Throwable) {
Toast.makeText(applicationContext,"error", Toast.LENGTH_LONG).show()
}
})
val textView: TextView = findViewById(R.id.test) as TextView
dbcategories.categoriesDAO().getALL().observeForever({categories ->
if(categories.size>0){
textView.text= categories[0].title
}
})
dbcategories.categoriesDAO().getALL().observe(this, {categories ->
if(categories.size>0){
textView.text= categories[0].title
}
}
} ```
I was trying to use a CameraX PreviewView inside of a Composable via AndroidView, but the preview is stretched and the right half is clipped as you can see in the screenshot.
Nevertheless the view seems to occupy the correct space.
This problem only occurs in portrait mode.
I was able to reproduce the problem on two cell phones and in the emulator so I doubt it is hardware specific.
I have tried different scale types, but that does not seem to affect the stretching.
I would report a bug, but I am not sure whether this is a bug in compose or cameraX.
See the code I use below:
package com.example.camerapreview
import android.Manifest
import android.graphics.Color
import android.os.Bundle
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
import android.widget.LinearLayout
import androidx.activity.ComponentActivity
import androidx.activity.compose.registerForActivityResult
import androidx.activity.compose.setContent
import androidx.activity.result.contract.ActivityResultContracts
import androidx.camera.core.AspectRatio.RATIO_16_9
import androidx.camera.core.CameraSelector
import androidx.camera.lifecycle.ProcessCameraProvider
import androidx.camera.view.PreviewView
import androidx.compose.material.Button
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.runtime.*
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.viewinterop.AndroidView
import androidx.core.content.ContextCompat
import androidx.lifecycle.LifecycleOwner
import com.example.pointergun.ui.theme.PointerGunTheme
import com.google.common.util.concurrent.ListenableFuture
import androidx.camera.core.Preview
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val cameraProviderFuture = ProcessCameraProvider.getInstance(this)
setContent {
PointerGunTheme {
Surface(color = MaterialTheme.colors.background) {
var isPermissionGranted by remember {
mutableStateOf<Boolean?>(null)
}
val launcher =
registerForActivityResult(contract = ActivityResultContracts.RequestPermission()) { isGranted ->
isPermissionGranted = isGranted
}
when (isPermissionGranted) {
true -> CameraPreview(cameraProviderFuture)
false -> Text("permission pls")
null -> Button(onClick = { launcher.launch(Manifest.permission.CAMERA) }) {
Text(text = "Start!")
}
}
}
}
}
}
}
fun bindPreview(
cameraProvider: ProcessCameraProvider,
lifecycleOwner: LifecycleOwner,
previewView: PreviewView,
) {
val preview: Preview = Preview.Builder()
.setTargetAspectRatio(RATIO_16_9)
.build()
val cameraSelector: CameraSelector = CameraSelector.Builder()
.requireLensFacing(CameraSelector.LENS_FACING_BACK)
.build()
preview.setSurfaceProvider(previewView.surfaceProvider)
var camera = cameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, preview)
}
#Composable
fun CameraPreview(cameraProviderFuture: ListenableFuture<ProcessCameraProvider>) {
val lifecycleOwner = LocalLifecycleOwner.current
AndroidView(
factory = { context ->
PreviewView(context).apply {
setBackgroundColor(Color.GREEN)
layoutParams = LinearLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT)
scaleType = PreviewView.ScaleType.FILL_START
post {
cameraProviderFuture.addListener(Runnable {
val cameraProvider = cameraProviderFuture.get()
bindPreview(
cameraProvider,
lifecycleOwner,
this,
)
}, ContextCompat.getMainExecutor(context))
}
}
}
)
}
Need to set the implementation mode for the PreviewView
implementationMode = PreviewView.ImplementationMode.COMPATIBLE
In your case :
PreviewView(context).apply {
setBackgroundColor(Color.GREEN)
layoutParams = LinearLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT)
scaleType = PreviewView.ScaleType.FILL_START
implementationMode = PreviewView.ImplementationMode.COMPATIBLE
post {
cameraProviderFuture.addListener(Runnable {
val cameraProvider = cameraProviderFuture.get()
bindPreview(
cameraProvider,
lifecycleOwner,
this,
)
}, ContextCompat.getMainExecutor(context))
}
}
So i was trying to get some text modifier with padding and it was all going good until I imported androidx.compose.foundation.layout.padding and the error on Modifier.padding(10.dp) didnt disapear, i tried searching if the import is moved/deprecated but i didnt see any changed releated to it. It also tells me the import isnt used so im really confused.
I use:
Android Studio - Arctic Fox 2020.3.1 canary 1
Kotlin Plugin - 1.4.10-Studio4.2-1\
My full code:
package com.example.weather
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.foundation.Text
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.padding
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Surface
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.setContent
import androidx.compose.ui.unit.dp
import androidx.ui.tooling.preview.Preview
import com.example.weather.ui.ExampleWeatherTheme
import java.lang.reflect.Modifier
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
ExampleWeatherTheme {
Surface(color = MaterialTheme.colors.background) {
Column(modifier = Modifier.padding(10.dp)) {
FeelsLike(50)
}
}
}
}
}
}
#Composable
fun FeelsLike(feelstemp: Int) {
Text(text = "Feels Like: $feelstempĀ°")
}
#Composable
fun Temperature(temp: Int) {
Text(text = "$temp")
}
#Preview(showBackground = true)
#Composable
fun BasicPreview() {
ExampleWeatherTheme(darkTheme = true) {
FeelsLike(50)
}
}
This is your issue:
import java.lang.reflect.Modifier
You are importing the wrong Modifier class. It happened to me and the error can be quite missleading. Change the import to:
import androidx.compose.ui.Modifier