In AndroidManifest.xml, we need to declare our Activity and we can provide a label, i.e.
<activity android:name="MyActivityClass"
android:label="Activity Label" />
In Jetpack Compose, how can I get that Activity Label?
I tried to get from LocalContext.current but can't find it.
#Composable
fun TopBar() {
val context = LocalContext.current
val title = context.getActivity().getTitle() // Wrong API... not there
TopAppBar(
title = { Text(text = title, fontSize = 18.sp) },
backgroundColor = MaterialTheme.colors.primary,
contentColor = Color.White
)
}
The label provided for an Activity in the AndroidManifest.xml can be accessed in general through PackageManager.getActivityInfo(...).
Since you are using Compose anyway, here is a Kotlin flavoured extension function
inline fun <reified T> Context.getActivityLabel(): String {
val componentName = ComponentName(this, T::class.java)
val activityInfo = packageManager.getActivityInfo(componentName, 0)
// loadLabel takes care of cases when the label is specified as a String literal
// as well as cases when the label is specified as a String resource
return activityInfo.loadLabel(packageManager).toString()
}
Usage inside a Composable
val context = LocalContext.current
val title = context.getActivityLabel<MyActivityClass>()
Usage elsewhere
val context: Context = //...
val title = context.getActivityLabel<MyActivityClass>()
I got it through the below approach
#Composable
fun TopBar() {
val activity = LocalContext.current as Activity
val title = activity.title?.toString() ?: "No Title"
TopAppBar(
title = { Text(text = title, fontSize = 18.sp) },
backgroundColor = MaterialTheme.colors.primary,
contentColor = Color.White
)
}
Related
I want to add some semantics
contentDescription = "SomeID"
testTag = "SomeID"
to some UI elements
Current approach is like this
modifier = Modifier.padding(top = 10).semantics {
testTag = "SomeID"
contentDescription = "SomeID"
},
How do write a custom extension that takes in input data and assigns it to semantics
modifier = Modifier.padding(top = 10).addSemantics(id = "SomeID"),
You can do it as
fun Modifier.customModifier(paddingValues: PaddingValues, description: String) = this.then(
padding(paddingValues).semantics {
testTag = description
contentDescription = description
}
)
When you wish your Modifier to have memory or access to Comopsable scope to use LaunchedEffect and so on you can use composed as
fun Modifier.customModifierWithMemory(paddingValues: PaddingValues, description: String) =
composed {
LaunchedEffect(key1 = Unit){
// Do something here
}
var memory by remember {
mutableStateOf(0)
}
Modifier
.padding(paddingValues)
.semantics {
testTag = description
contentDescription = description
}
}
I can't find how to linkify my Text() using Jetpack Compose.
Before compose all I had to do was:
Linkify.addLinks(myTextView, Linkify.EMAIL_ADDRESSES or Linkify.WEB_URLS)
And all the links contained in my TextView were becoming clickable links, obviously.
Important: The content of the Text is coming from an API and the links do not have a fixed position and content may contain multiple links.
I want to keep this behavior with using Jetpack Compose but I can't find any information about doing that.
Does anyone know?
In case someone is looking for a solution, the following will make any links clickable and styled in your text:
#Composable
fun LinkifyText(text: String, modifier: Modifier = Modifier) {
val uriHandler = LocalUriHandler.current
val layoutResult = remember {
mutableStateOf<TextLayoutResult?>(null)
}
val linksList = extractUrls(text)
val annotatedString = buildAnnotatedString {
append(text)
linksList.forEach {
addStyle(
style = SpanStyle(
color = Color.Companion.Blue,
textDecoration = TextDecoration.Underline
),
start = it.start,
end = it.end
)
addStringAnnotation(
tag = "URL",
annotation = it.url,
start = it.start,
end = it.end
)
}
}
Text(text = annotatedString, style = MaterialTheme.typography.body1, modifier = modifier.pointerInput(Unit) {
detectTapGestures { offsetPosition ->
layoutResult.value?.let {
val position = it.getOffsetForPosition(offsetPosition)
annotatedString.getStringAnnotations(position, position).firstOrNull()
?.let { result ->
if (result.tag == "URL") {
uriHandler.openUri(result.item)
}
}
}
}
},
onTextLayout = { layoutResult.value = it }
)
}
private val urlPattern: Pattern = Pattern.compile(
"(?:^|[\\W])((ht|f)tp(s?):\\/\\/|www\\.)"
+ "(([\\w\\-]+\\.){1,}?([\\w\\-.~]+\\/?)*"
+ "[\\p{Alnum}.,%_=?&#\\-+()\\[\\]\\*$~#!:/{};']*)",
Pattern.CASE_INSENSITIVE or Pattern.MULTILINE or Pattern.DOTALL
)
fun extractUrls(text: String): List<LinkInfos> {
val matcher = urlPattern.matcher(text)
var matchStart: Int
var matchEnd: Int
val links = arrayListOf<LinkInfos>()
while (matcher.find()) {
matchStart = matcher.start(1)
matchEnd = matcher.end()
var url = text.substring(matchStart, matchEnd)
if (!url.startsWith("http://") && !url.startsWith("https://"))
url = "https://$url"
links.add(LinkInfos(url, matchStart, matchEnd))
}
return links
}
data class LinkInfos(
val url: String,
val start: Int,
val end: Int
)
I think the better solution for now is create your own component with textview like that:
#Composable
fun DefaultLinkifyText(modifier: Modifier = Modifier, text: String?) {
val context = LocalContext.current
val customLinkifyTextView = remember {
TextView(context)
}
AndroidView(modifier = modifier, factory = { customLinkifyTextView }) { textView ->
textView.text = text ?: ""
LinkifyCompat.addLinks(textView, Linkify.ALL)
Linkify.addLinks(textView, Patterns.PHONE,"tel:",
Linkify.sPhoneNumberMatchFilter, Linkify.sPhoneNumberTransformFilter)
textView.movementMethod = LinkMovementMethod.getInstance()
}
}
You can still use Linkify.addLinks but convert the result into AnnotatedString like this:
fun String.linkify(
linkStyle: SpanStyle,
) = buildAnnotatedString {
append(this#linkify)
val spannable = SpannableString(this#linkify)
Linkify.addLinks(spannable, Linkify.WEB_URLS)
val spans = spannable.getSpans(0, spannable.length, URLSpan::class.java)
for (span in spans) {
val start = spannable.getSpanStart(span)
val end = spannable.getSpanEnd(span)
addStyle(
start = start,
end = end,
style = linkStyle,
)
addStringAnnotation(
tag = "URL",
annotation = span.url,
start = start,
end = end
)
}
}
fun AnnotatedString.urlAt(position: Int, onFound: (String) -> Unit) =
getStringAnnotations("URL", position, position).firstOrNull()?.item?.let {
onFound(it)
}
Use it in your composable like this:
val linkStyle = SpanStyle(
color = MaterialTheme.colors.primary,
textDecoration = TextDecoration.Underline,
)
ClickableText(
text = remember(text) { text.linkify(linkStyle) },
onClick = { position -> text.urlAt(position, onClickLink) },
)
You can use AnnotatedString to achieve this behavior.
docs: https://developer.android.com/reference/kotlin/androidx/compose/ui/text/AnnotatedString
Also, this one may help you:
AutoLink for Android Compose Text
Based on above answers,
You can use https://github.com/firefinchdev/linkify-text
Its a single file, you can directly copy it to your project.
Also, it uses Android's Linkify for link detection, which is same as that of TextView's autoLink.
A similar, but simpler solution that I went with to get the proper Material Design look and feel:
#Composable
fun BodyWithLinks(body: String, modifier: Modifier = Modifier) {
AndroidView(
modifier = modifier,
factory = { context ->
(MaterialTextView(context) as AppCompatTextView).apply {
val spannableString = SpannableString(body)
Linkify.addLinks(spannableString, Linkify.ALL)
text = spannableString
setTextAppearance(R.style.Theme_Body_1)
}
},
)
}
This is an example if you have multiple clickable words in one sentence and you want to navigate inside the application:
#Composable
fun InformativeSignUpText() {
val informativeText = stringResource(R.string.sign_up_already_have_an_account)
val logInSubstring = stringResource(R.string.general_log_in)
val supportSubstring = stringResource(R.string.general_support)
val logInIndex = informativeText.indexOf(logInSubstring)
val supportIndex = informativeText.indexOf(supportSubstring)
val informativeAnnotatedText = buildAnnotatedString {
append(informativeText)
addStyle(
style = SpanStyle(
color = MaterialTheme.colors.primary
),
start = logInIndex,
end = logInIndex + logInSubstring.length
)
addStringAnnotation(
tag = logInSubstring,
annotation = logInSubstring,
start = logInIndex,
end = logInIndex + logInSubstring.length
)
addStyle(
style = SpanStyle(
color = MaterialTheme.colors.primary
),
start = supportIndex,
end = supportIndex + supportSubstring.length
)
addStringAnnotation(
tag = supportSubstring,
annotation = supportSubstring,
start = supportIndex,
end = supportIndex + supportSubstring.length
)
}
ClickableText(
modifier = Modifier.padding(
top = 16.dp
),
style = MaterialTheme.typography.subtitle1.copy(
color = Nevada
),
text = informativeAnnotatedText,
onClick = { offset ->
informativeAnnotatedText.getStringAnnotations(
tag = logInSubstring,
start = offset,
end = offset
).firstOrNull()?.let {
Log.d("mlogs", it.item)
}
informativeAnnotatedText.getStringAnnotations(
tag = supportSubstring,
start = offset,
end = offset
).firstOrNull()?.let {
Log.d("mlogs", it.item)
}
}
)
}
Suppose you already have a Spanned that potentially contains clickable spans (i.e. you've already done the linkify part), then you can use this:
#Composable
fun StyledText(text: CharSequence, modifier: Modifier = Modifier) {
val clickable = rememberSaveable {
text is Spanned && text.getSpans(0, text.length, ClickableSpan::class.java).isNotEmpty()
}
AndroidView(
modifier = modifier,
factory = { context ->
TextView(context).apply {
if (clickable) {
movementMethod = LinkMovementMethod.getInstance()
}
}
},
update = {
it.text = text
}
)
}
This will also render any other span types that may be there.
Is there any way to use android:autoLink feature on JetPack Compose Text?
I know, that it is maybe not "declarative way" for using this feature in one simple tag/modifier, but maybe there is some easy way for this?
For styling text I can use this way
val apiString = AnnotatedString.Builder("API provided by")
apiString.pushStyle(
style = SpanStyle(
color = Color.Companion.Blue,
textDecoration = TextDecoration.Underline
)
)
apiString.append("https://example.com")
Text(text = apiString.toAnnotatedString())
But, how can I manage clicks here? And would be great, if I programatically say what behaviour I expect from the system (email, phone, web, etc). Like it. works with TextView.
Thank you
We can achieve Linkify kind of TextView in Android Compose like this example below,
#Composable
fun LinkifySample() {
val uriHandler = UriHandlerAmbient.current
val layoutResult = remember {
mutableStateOf<TextLayoutResult?>(null)
}
val text = "API provided by"
val annotatedString = annotatedString {
pushStyle(
style = SpanStyle(
color = Color.Companion.Blue,
textDecoration = TextDecoration.Underline
)
)
append(text)
addStringAnnotation(
tag = "URL",
annotation = "https://example.com",
start = 0,
end = text.length
)
}
Text(
fontSize = 16.sp,
text = annotatedString, modifier = Modifier.tapGestureFilter { offsetPosition ->
layoutResult.value?.let {
val position = it.getOffsetForPosition(offsetPosition)
annotatedString.getStringAnnotations(position, position).firstOrNull()
?.let { result ->
if (result.tag == "URL") {
uriHandler.openUri(result.item)
}
}
}
},
onTextLayout = { layoutResult.value = it }
)
}
In the above example, we can see we give the text and also we use addStringAnnotation to set the tag. And using tapGestureFilter, we can get the clicked annotation.
Finally using UriHandlerAmbient.current we can open the link like email, phone, or web.
Reference : https://www.hellsoft.se/rendering-markdown-with-jetpack-compose/
The most important part of jetpack compose is the compatibility with native android components.
Create a component that use TextView and use it:
#Composable
fun DefaultLinkifyText(modifier: Modifier = Modifier, text: String?) {
val context = LocalContext.current
val customLinkifyTextView = remember {
TextView(context)
}
AndroidView(modifier = modifier, factory = { customLinkifyTextView }) { textView ->
textView.text = text ?: ""
LinkifyCompat.addLinks(textView, Linkify.ALL)
Linkify.addLinks(textView, Patterns.PHONE,"tel:",
Linkify.sPhoneNumberMatchFilter, Linkify.sPhoneNumberTransformFilter)
textView.movementMethod = LinkMovementMethod.getInstance()
}
}
How to use:
DefaultLinkifyText(
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight(),
text = "6999999 and https://stackoverflow.com/ works fine"
)
I want to use custom colors defined in the colors.xml class directly without using the Material theme colors or the default theme provided by the Jetpack. Is there any straightforward way to do it?
You can use colorResource() which loads a color resource.
Text(
text = "Hello World",
color = colorResource(R.color.purple_200)
)
To use color in jetpack compose using recommended create a package ui.theme in com.<domain_name>.<app_name> which will likely be present by default if you are creating empty compose project. Now create Color.kt and Theme.kt kotlin files if they are not present in your project.
In Color.kt add the colors you need
package com.<domain_name>.<app_name>.ui.theme
import androidx.compose.ui.graphics.Color
val Purple200 = Color(0xFFBB86FC)
val Purple500 = Color(0xFF6200EE)
val Purple700 = Color(0xFF3700B3)
val Teal200 = Color(0xFF03DAC5)
val Flower = Color(0xFF4CAF50)
val Deer = Color(0xFFFF5722)
val Mango = Color(0xFFFF9800)
val AppbarColor = Color(0xFF2196F3)
Here is ready to use a Material Color template made by me
There are 3 common ways of using colors
Method 1 : Directly use color
import com.<domain_name>.<app_name>.ui.theme.*
Text(text = "Hello ", color = Flower)
Method 2 : Override default MaterialTheme colors
Now in, Theme.kt
private val DarkColorPalette = darkColors(
primary = Purple200,
primaryVariant = Purple700,
secondary = Teal200,
onBackground = Flower //Custom color
)
private val LightColorPalette = lightColors(
primary = Purple500,
primaryVariant = Purple700,
secondary = Teal200,
onBackground = Deer //Custom color
/* Other default colors to override
background = Color.White,
surface = Color.White,
onPrimary = Color.White,
onSecondary = Color.Black,
onBackground = Color.Black,
onSurface = Color.Black,
*/
)
#Composable
fun NotepadTheme(darkTheme: Boolean = isSystemInDarkTheme(),
content:#Composable() () -> Unit) {
val colors = if (darkTheme) {
DarkColorPalette
} else {
LightColorPalette
}
MaterialTheme(
colors = colors,
typography = Typography,
shapes = Shapes,
content = content
)
}
MainActivity.kt
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
ColorApp()
}
}
}
#Composable
fun ColorApp() {
ColorTheme {
Surface(modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colors.background) {
Greeting("Android")
}
}
}
#Composable
fun Greeting(name: String) {
Text(text = "Hello $name!", color = MaterialTheme.colors.onBackground) //Using color
}
#Preview(
showBackground = true, name = "Light mode",
uiMode = Configuration.UI_MODE_NIGHT_NO or
Configuration.UI_MODE_TYPE_NORMAL
)
#Preview(
showBackground = true, name = "Night mode",
uiMode = Configuration.UI_MODE_NIGHT_YES or
Configuration.UI_MODE_TYPE_NORMAL
)
#Composable
fun DefaultPreview() {
ColorApp()
}
Method 3 : Custom theme (Recommended method)
Text(text = "Hello ", color = AppNameTheme.colors.customColor)
Let's suppose I'm using some library that's intended to provide some UI Widgets.
Let's say this library provides a Button Widget called FancyButton.
In the other hand, I have a new project created with Android Studio 4 that allows me to create a new project with an Empty Compose Activity.
The question is:
How should I add this FancyButton to the view stack? Is it possible? Or with Jetpack Compose I can only use components that had been developed specifically for Jetpack Compose. In this case, AFAIK I could only use Android standars components (Text, MaterialTheme, etc).
If I try to use something like this:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MaterialTheme {
Greeting("Android")
FancyButton(context, "Some text")
}
}
}
then I get this error:
e: Supertypes of the following classes cannot be resolved. Please make sure you have the required dependencies in the classpath.
Currently (as of 0.1.0-dev04), there is not a good solution to this. In the future, you'll be able to simply call it as FancyButton("Some text") (no context needed).
You can see a demo of what it will look like in the Compose code here.
Update in alpha 06
It is possible to import Android View instances in a composable.
Use ContextAmbient.current as the context parameter for the View.
Column(modifier = Modifier.padding(16.dp)) {
// CustomView using Object
MyCustomView(context = ContextAmbient.current)
// If the state updates
AndroidView(viewBlock = ::CustomView, modifier = modifier) { customView ->
// Modify the custom view
}
// Using xml resource
AndroidView(resId = R.layout.view_demo)
}
You can wrap your custom view within the AndroidView composable:
#Composable
fun RegularTextView() {
AndroidView(
factory = { context ->
TextView(context).apply {
text = "RegularTextView"
textSize = 34.dp.value
}
},
)
}
And here is how to update your custom view during a recomposition, by using the update parameter:
#Composable
fun RegularTextView() {
var string by remember {
mutableStateOf("RegularTextView")
}
Column(horizontalAlignment = Alignment.CenterHorizontally) {
AndroidView(
factory = { context ->
TextView(context).apply {
textSize = 34.dp.value
}
},
update = { textView ->
textView.text = string
}
)
Spacer(modifier = Modifier.height(8.dp))
Button(
onClick = {
string = "Button clicked"
},
) {
Text(text = "Update text")
}
}
}
#Composable
fun ButtonType1(text: String, onClick: () -> Unit)
{
Button (
modifier=Modifier.fillMaxWidth().height(50.dp),
onClick = onClick,
shape = RoundedCornerShape(5.dp),
border = BorderStroke(3.dp, colorResource(id = R.color.colorPrimaryDark)),
colors = ButtonDefaults.buttonColors(contentColor = Color.White, backgroundColor = colorResource(id = R.color.colorPrimaryDark))
)
{
Text(text = text , color = colorResource(id = R.color.white),
fontFamily = montserrat,
fontWeight = FontWeight.Normal,
fontSize = 15.sp
)
}
}