I need to go back from RegisterUserScreen to Login
class RegisterUser : Fragment() {
private val viewModel: RegistrationViewModel by viewModels()
#SuppressLint("InflateParams")
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
): View {
return ComposeView(requireContext()).apply {
setContent {
RegisterScreen(viewModel, this#RegisterUser)
}
}
}
}
In RegisterScreen I have Icon with clickable property:
Icon(
painter = painterResource(id = R.drawable.ic_baseline_arrow_back_24),
modifier = Modifier
.align(Alignment.Start)
.width(40.dp)
.height(30.dp)
.clickable { findNavController(registerUserFragment).navigate(R.id.action_registerUser_to_logIn) },
contentDescription = stringResource(id = R.string.register_user_button_back)
)
Problem is when I click on it nothing happens.
EDIT:
Failed to open APK
'/data/app/~~Jg0TKmfn-xZKpM84xgQqlw==/android.tvz.hr.fitnessclienttracker-DRhW5UElzP1jmYnzMDYMpQ==/base.apk':
I/O error 2022-12-22 20:37:16.666 742-742 ndroid.systemu
pid-742 E Failed to open APK
'/data/app/~~Jg0TKmfn-xZKpM84xgQqlw==/android.tvz.hr.fitnessclienttracker-DRhW5UElzP1jmYnzMDYMpQ==/base.apk':
I/O error 2022-12-22 20:37:16.667 742-742 ResourcesManager
pid-742 E failed to add asset path
'/data/app/~~Jg0TKmfn-xZKpM84xgQqlw==/android.tvz.hr.fitnessclienttracker-DRhW5UElzP1jmYnzMDYMpQ==/base.apk'
java.io.IOException: Failed to load asset path
/data/app/~~Jg0TKmfn-xZKpM84xgQqlw==/android.tvz.hr.fitnessclienttracker-DRhW5UElzP1jmYnzMDYMpQ==/base.apk
Related
I have a compose screen in a fragment, and in that screen there is a button.
I want to dismiss the fragment/go back to previous screen when this button is pressed.
But I can't access any activity/fragment methods inside onClick.
How can I do that?
#AndroidEntryPoint
class MyFragment #Inject constructor() : Fragment(){
#ExperimentalComposeUiApi
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
return ComposeView(requireContext()).apply {
this.setContent {
Button(
onClick = {
//Dismiss fragment.
},
) {
Text(
"Click me"
)
}
}
}
}
}
You can get the LocalOnBackPressedDispatcherOwner inside any composable, and use onBackPressed() or navigate in an other way:
val onBackPressedDispatcher = LocalOnBackPressedDispatcherOwner.current?.onBackPressedDispatcher
Button(
onClick = {
onBackPressedDispatcher?.onBackPressed()
},
) {
Text(
"Click me"
)
}
Solved like this:
#AndroidEntryPoint
class MyFragment #Inject constructor(
) : Fragment(){
#ExperimentalComposeUiApi
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
return ComposeView(requireContext()).apply {
this.setContent {
val shouldDismiss = remember { mutableStateOf(false) }
if (shouldDismiss) {
dismissFragment()
}else{
Button(
onClick = {
shouldDismiss.value = true
},
) {
Text(
"Click me"
)
}
}
}
}
}
private fun dismissFragment(){
activity?.onBackPressed()
}
}
I was using this library to get signature https://github.com/gcacace/android-signaturepad with Android app written in Kotlin, now I moved to jetpack compose and I want to use it with the new UI library
my old code was:
XML
<com.github.gcacace.signaturepad.views.SignaturePad
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/signature_pad"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:penColor="#android:color/black"
/>
Fragment
class SignatureFragment : Fragment() {
private lateinit var signatureBinding: FragmentSignatureBinding
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
setupSignatureBinding(inflater, container)
handleSignatureClicks()
return signatureBinding.root
}
private fun setupSignatureBinding(inflater: LayoutInflater, container: ViewGroup?) {
signatureBinding = DataBindingUtil.inflate(
inflater,
R.layout.fragment_signature,
container,
false
)
}
private fun handleSignatureClicks() {
signatureBinding.signaturePad.setOnSignedListener(object : SignaturePad.OnSignedListener {
override fun onStartSigning() {}
override fun onSigned() {
signatureBinding.saveButton.isEnabled = true
signatureBinding.clearButton.isEnabled = true
}
override fun onClear() {
signatureBinding.saveButton.isEnabled = false
signatureBinding.clearButton.isEnabled = false
}
})
signatureBinding.clearButton.setOnClickListener{ signatureBinding.signaturePad.clear() }
signatureBinding.saveButton.setOnClickListener {
val signatureBitmap: Bitmap = signatureBinding.signaturePad.signatureBitmap
}
}
}
I was thinking about solving this problem and I found that I could go from the compose screen to activity with intent, and I did that at the code below, but the problem is I can't back to the form to complete the rest of the questions but instead when clicking on the back button it goes to the main activity of the app
#Composable
fun SignatureQuestion(question: QuestionModel, formViewModel: FormViewModel) {
val context = LocalContext.current
CustomInputFieldContainer(
isRequired = question.is_required,
label = question.question_title
) {
Button(
onClick = {
val intent = Intent(context, SignatureActivity::class.java)
intent.putExtra("question", question)
context.startActivity(intent)
},
contentPadding = PaddingValues(),
modifier = Modifier.background(Color.Yellow)
) {
Row(
modifier = Modifier
.fillMaxWidth()
.wrapContentSize(Alignment.BottomCenter)
.padding(vertical = 10.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(imageVector = Icons.Filled.Edit, contentDescription = null)
Spacer(modifier = Modifier.width(8.dp))
Text(text = "Add Signature")
}
}
}
}
Could anyone help me solve this problem?
I have been working on a updated version of this library since the original author don't seems to have no time to do it. Find updated version with Jetpack Compose support here: https://github.com/warting/android-signaturepad
If you want to access xml layout then follow below code as i did with your mentioned library:
1st create xml layout with your library tag.
and then write below lines in your #composable function:
val parser: XmlPullParser = context.resources.getLayout(R.layout.test_layout)
try {
parser.next()
parser.nextTag()
} catch (e: Exception) {
e.printStackTrace()
}
val attr: AttributeSet = Xml.asAttributeSet(parser)
val signature= remember {
SignaturePad(context,attr).apply {
id=R.id.signature_pad
}
}
AndroidView({signature}) { signaturepad->
// here you can play with signature pad
}
i'm new in android jetpack compose
i would like to implement Shimmer effect for Android. as per given in this documentation
it's working fine with xml approach, but i want to do same with compose function (in short embed XML into composable function).
Here is XML Code: shimmer_view.xml
<com.facebook.shimmer.ShimmerFrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:context=".presentation.ui.recipe_list.UserListFragment"
android:id="#+id/shimmerFrameLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:shimmer_auto_start="true"
>
<include layout="#layout/shimmer_placeholder_card" />
</com.facebook.shimmer.ShimmerFrameLayout>
Fragment where i want to use above xml file
class UserListFragment: Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
// Traditional Approach Working Fine.
// val view = inflater.inflate(R.layout.shimmer_view, container, false)
// return view
// ComposeView inside fragment
val composeView = ComposeView(requireContext()).apply {
setContent {
Text(text = "Welcome in Compose-World ")
// Here i want To use xml file as a Compose View
}
}
return composeView
}
}
is it possible to inflate or Convert shimmer_view.xml into composable function?
OR
somehow emmbed this xml into compose function.
for reference please share sample code if any. it will help us.
Thank you
I created this function that help me to inflate an XML layout as a composable instance:
#Composable
fun createAndroidViewForXMLLayout(#LayoutRes resId: Int) {
val context = LocalContext.current
val your_xml_Layout = remember(resId, context) {
LayoutInflater.from(context).inflate(resId,null)
}
AndroidView({ your_xml_Layout })
}
In your case, you just have to call this function like here:
class UserListFragment: Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
val composeView = ComposeView(requireContext()).apply {
setContent {
Text(text = "Welcome in Compose-World ")
// Here you add the function with the id of your layout **"R.layout.shimmer_view"**
createAndroidViewForXMLLayout(R.layout.shimmer_view)
}
}
return composeView
}
}
Here's a composable that gives you the shimmer effect inside another composable:
#Composable
fun Shimmer(modifier: Modifier = Modifier, content: #Composable () -> Unit) {
val context = LocalContext.current
val shimmer = remember {
ShimmerFrameLayout(context).apply {
addView(ComposeView(context).apply {
setContent(content)
})
}
}
AndroidView(
modifier = modifier,
factory = { shimmer }
) { it.startShimmer() }
}
And use it as:
Shimmer {
// Your placeholder
Box(modifier = Modifier.size(100.dp).background(Color.LightGray))
}
I am trying to create a list of items in Jetpack Compose that automatically updates whenever the underlying data changes, but it is not updating and I cannot add an item to the LazyColumnFor after it has initially composed.
private var latestMessages = mutableListOf<ChatMessage>()
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
return ComposeView(context = requireContext()).apply {
setContent {
LatestMessages(latestMessages)
}
}
}
#Composable
fun LatestMessages(messages: MutableList<ChatMessage>) {
Column {
LazyColumnFor(items = messages) { chatMessage ->
LatestMessageItem(chatMessage) {
// onclick
}
}
}
Box(alignment = Alignment.Center) {
Button(onClick = {
latestMessages.add(
ChatMessage(
"testId",
"testText2",
"testFromId2",
"testToId",
"timestamp",
0
)
)
}) {
}
}
}
The Button in the Box is just to test adding an item. From what I understand LazyColumnFor should update whenever the underlying data is changed, similar to ForEach in SwiftUI. What am I doing wrong?
Edit March 2022
Jetpack compose no longer uses LazyColumnFor so I've updated the answer to reflect the current state of compose.
Jetpack compose always needs a state object to be modified before recomposition can occur. You need to use a MutableStateList<T>. You can pass a reference to it around and update in any method which accepts a SnapShotStateList<T>
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
return ComposeView(context = requireContext()).apply {
setContent {
val latestMessages = mutableStateListOf<ChatMessage>()
LatestMessages(latestMessages)
}
}
}
#Composable
fun LatestMessages(messages: SnapshotStateList<ChatMessage>) {
Column {
LazyColumn {
items(messages){ chatMessage ->
LatestMessageItem(chatMessage) {
// onclick
}
}
}
Box(alignment = Alignment.Center) {
Button(onClick = {
latestMessages.add(
ChatMessage(
"testId",
"testText2",
"testFromId2",
"testToId",
"timestamp",
0
)
)
}) {
}
}
}
The documentation describes how to create UI Jetpack Compose inside Activity.
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
Text("Hello world!")
}
}
}
But how can I use it inside fragment?
setContent on ViewGroup is now deprecated.
The below is accurate as of Compose v1.0.0-alpha01.
For pure compose UI Fragment:
class ComposeUIFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return ComposeView(requireContext()).apply {
setContent {
Text(text = "Hello world.")
}
}
}
}
For hybrid compose UI Fragment - add ComposeView to xml layout, then:
class ComposeUIFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_compose_ui, container, false).apply {
findViewById<ComposeView>(R.id.composeView).setContent {
Text(text = "Hello world.")
}
}
}
}
You don't need Fragments with Compose. You can navigate to another screen without needing a Fragment or an Activity:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
val navController = rememberNavController()
NavHost(navController, startDestination = "welcome") {
composable("welcome") { WelcomeScreen(navController) }
composable("secondScreen") { SecondScreen() }
}
}
}
}
#Composable
fun WelcomeScreen(navController: NavController) {
Column {
Text(text = "Welcome!")
Button(onClick = { navController.navigate("secondScreen") }) {
Text(text = "Continue")
}
}
}
#Composable
fun SecondScreen() {
Text(text = "Second screen!")
}
With 1.0.x you can :
- Define a ComposeView in the xml-layout.
add a androidx.compose.ui.platform.ComposeView in your layout-xml files:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
android:orientation="vertical"
...>
<TextView ../>
<androidx.compose.ui.platform.ComposeView
android:id="#+id/compose_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
Then get the ComposeView using the XML ID, set a Composition strategy and call setContent():
class ExampleFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentExampleBinding.inflate(inflater, container, false)
val view = binding.root
view.composeView.apply {
// Dispose the Composition when viewLifecycleOwner is destroyed
setViewCompositionStrategy(
DisposeOnLifecycleDestroyed(viewLifecycleOwner)
)
setContent {
// In Compose world
MaterialTheme {
Text("Hello Compose!")
}
}
}
return view
}
/** ... */
}
- Include a ComposeView directly in a fragment.
class ExampleFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
return ComposeView(requireContext()).apply {
// Dispose the Composition when viewLifecycleOwner is destroyed
setViewCompositionStrategy(
DisposeOnLifecycleDestroyed(viewLifecycleOwner)
)
setContent {
MaterialTheme {
// In Compose world
Text("Hello Compose!")
}
}
}
}
}
Found it:
class LoginFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
val fragmentView = inflater.inflate(R.layout.fragment_login, container, false)
(fragmentView as ViewGroup).setContent {
Hello("Jetpack Compose")
}
return fragmentView
}
#Composable
fun Hello(name: String) = MaterialTheme {
FlexColumn {
inflexible {
// Item height will be equal content height
TopAppBar( // App Bar with title
title = { Text("Jetpack Compose Sample") }
)
}
expanded(1F) {
// occupy whole empty space in the Column
Center {
// Center content
Text("Hello $name!") // Text label
}
}
}
}
}
On my mind if you want to use Jetpack Compose with fragments in a pretty way like this
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
) = contentView {
Text("Hello world")
}
you can create you own extension functions for Fragments
fun Fragment.requireContentView(
compositionStrategy: ViewCompositionStrategy = DisposeOnDetachedFromWindow,
context: Context = requireContext(),
content: #Composable () -> Unit
): ComposeView {
val view = ComposeView(context)
view.setViewCompositionStrategy(compositionStrategy)
view.setContent(content)
return view
}
fun Fragment.contentView(
compositionStrategy: ViewCompositionStrategy = DisposeOnDetachedFromWindow,
context: Context? = getContext(),
content: #Composable () -> Unit
): ComposeView? {
context ?: return null
val view = ComposeView(context)
view.setViewCompositionStrategy(compositionStrategy)
view.setContent(content)
return view
}
I like this approach because it looks similar to Activity's setContent { } extension
Also you can define another CompositionStrategy
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
) = contentView(DisposeOnLifecycleDestroyed(viewLifecycleOwner)) {
Text("Hello world")
}