question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Have you considered creating a compose wrapper over CalendarView? I’m not talking about total rewrite, but about a separate artifact with CalendarView as composable function. It would be nothing more then a wrapper using AndroidView, but until some new fully compose calendar will stabilize - this should work just fine.

If you’re fine with that i think i’ll try and find some time to create a PR with that

Issue Analytics

  • State:closed
  • Created 2 years ago
  • Reactions:3
  • Comments:11 (5 by maintainers)

github_iconTop GitHub Comments

1reaction
kizitonwosecommented, Oct 15, 2022

Hi @jakoss Compose support is now available in version 2.0.0

1reaction
jakosscommented, Sep 22, 2021

Initial code:

@Composable
fun CalendarView(
    modifier: Modifier = Modifier,
    currentMonth: YearMonth,
    firstMonth: YearMonth,
    lastMonth: YearMonth,
    firstDayOfWeek: DayOfWeek,
    daySize: Size = CalendarView.SIZE_SQUARE,
    scrollMode: ScrollMode = ScrollMode.CONTINUOUS,
    orientation: Orientation = Orientation.VERTICAL,
    maxRowCount: Int = 6,
    inDateStyle: InDateStyle = InDateStyle.ALL_MONTHS,
    outDateStyle: OutDateStyle = OutDateStyle.END_OF_ROW,
    hasBoundaries: Boolean = true,
    wrappedPageHeightAnimationDuration: Int = 200,
    calendarViewState: CalendarViewState = rememberCalendarViewState(),
    monthScrollEvent: ((CalendarMonth) -> Unit)? = null,
    monthHeaderContent: (@Composable (CalendarMonth) -> Unit)? = null,
    monthFooterContent: (@Composable (CalendarMonth) -> Unit)? = null,
    dayContent: @Composable (CalendarDay) -> Unit,
) {
    var firstMonthState by remember {
        mutableStateOf(firstMonth)
    }
    var lastMonthState by remember {
        mutableStateOf(lastMonth)
    }
    AndroidView(
        modifier = modifier,
        factory = { context ->
            CalendarView(context).apply {
                calendarViewState.calendarView = WeakReference(this)
                dayViewResource = R.layout.compose_view_layout
                dayBinder = object : DayBinder<ComposeViewContainer> {
                    override fun create(view: View) = ComposeViewContainer(view)
                    override fun bind(container: ComposeViewContainer, day: CalendarDay) {
                        container.composeView.setContent {
                            dayContent(day)
                        }
                    }
                }
                if (monthHeaderContent != null) {
                    monthHeaderResource = R.layout.compose_view_layout
                    monthHeaderBinder = object : MonthHeaderFooterBinder<ComposeViewContainer> {
                        override fun create(view: View) = ComposeViewContainer(view)
                        override fun bind(container: ComposeViewContainer, month: CalendarMonth) {
                            container.composeView.setContent {
                                monthHeaderContent(month)
                            }
                        }
                    }
                }
                if (monthFooterContent != null) {
                    monthFooterResource = R.layout.compose_view_layout
                    monthFooterBinder = object : MonthHeaderFooterBinder<ComposeViewContainer> {
                        override fun create(view: View) = ComposeViewContainer(view)
                        override fun bind(container: ComposeViewContainer, month: CalendarMonth) {
                            container.composeView.setContent {
                                monthFooterContent(month)
                            }
                        }
                    }
                }

                this.daySize = daySize
                this.orientation = when (orientation) {
                    Orientation.VERTICAL -> RecyclerView.VERTICAL
                    Orientation.HORIZONTAL -> RecyclerView.HORIZONTAL
                }
                this.scrollMode = scrollMode
                this.maxRowCount = maxRowCount
                this.inDateStyle = inDateStyle
                this.outDateStyle = outDateStyle
                this.hasBoundaries = hasBoundaries
                this.wrappedPageHeightAnimationDuration = wrappedPageHeightAnimationDuration
                monthScrollListener = monthScrollEvent

                setup(firstMonth, lastMonth, firstDayOfWeek)
                scrollToMonth(currentMonth)
            }
        },
        update = { calendarView ->
            calendarView.daySize = daySize
            calendarView.orientation = when (orientation) {
                Orientation.VERTICAL -> RecyclerView.VERTICAL
                Orientation.HORIZONTAL -> RecyclerView.HORIZONTAL
            }
            calendarView.scrollMode = scrollMode
            calendarView.maxRowCount = maxRowCount
            calendarView.inDateStyle = inDateStyle
            calendarView.outDateStyle = outDateStyle
            calendarView.hasBoundaries = hasBoundaries
            if (firstMonthState != firstMonth || lastMonthState != lastMonth) {
                firstMonthState = firstMonth
                lastMonthState = lastMonth
                calendarView.updateMonthRange(firstMonth, lastMonth)
            }
        })
}

class ComposeViewContainer(view: View) : ViewContainer(view) {
    val composeView: ComposeView = view.findViewById(R.id.composeView)
}

enum class Orientation {
    VERTICAL,
    HORIZONTAL,
}

@Composable
fun rememberCalendarViewState(): CalendarViewState {
    return remember {
        CalendarViewState()
    }
}

class CalendarViewState internal constructor(){
    internal var calendarView: WeakReference<CalendarView>? = null

    fun scrollToMonth(month: YearMonth) {
        calendarView?.get()?.scrollToMonth(month)
    }

    fun smoothScrollToMonth(month: YearMonth) {
        calendarView?.get()?.smoothScrollToMonth(month)
    }

    fun scrollToDay(day: CalendarDay) {
        calendarView?.get()?.scrollToDay(day)
    }

    fun scrollToDate(date: LocalDate, owner: DayOwner = DayOwner.THIS_MONTH) {
        calendarView?.get()?.scrollToDate(date, owner)
    }

    fun smoothScrollToDay(day: CalendarDay) {
        calendarView?.get()?.smoothScrollToDay(day)
    }

    fun smoothScrollToDate(date: LocalDate, owner: DayOwner = DayOwner.THIS_MONTH) {
        calendarView?.get()?.smoothScrollToDate(date, owner)
    }
}

And sample usage:

@Composable
fun MainScreen() {
    var selectedDay by remember {
        mutableStateOf<LocalDate?>(null)
    }
    val calendarViewState = rememberCalendarViewState()
    CalendarView(
        modifier = Modifier.fillMaxSize(),
        currentMonth = YearMonth.now(),
        firstMonth = YearMonth.now().minusMonths(10),
        lastMonth = YearMonth.now().plusMonths(10),
        firstDayOfWeek = WeekFields.of(Locale.getDefault()).firstDayOfWeek,
        scrollMode = ScrollMode.PAGED,
        orientation = Orientation.HORIZONTAL,
        calendarViewState = calendarViewState,
        dayContent = { day ->
            Box(
                modifier = Modifier
                    .fillMaxSize()
                    .clickable {
                        if (selectedDay != day.date) {
                            selectedDay = day.date
                        } else if (selectedDay == day.date) {
                            selectedDay = null
                        }
                    }, contentAlignment = Alignment.Center
            ) {
                val color = when {
                    selectedDay == day.date -> Color.Red
                    day.owner == DayOwner.THIS_MONTH -> Color.Black
                    else -> Color.Gray
                }
                Text(text = day.day.toString(), color = color)
            }
        },
        monthHeaderContent = { calendarMonth ->
            Row(
                modifier = Modifier
                    .fillMaxWidth()
                    .padding(vertical = 8.dp),
                horizontalArrangement = Arrangement.SpaceBetween
            ) {
                Button(onClick = {
                    calendarViewState.smoothScrollToMonth(
                        calendarMonth.yearMonth.minusMonths(1)
                    )
                }) {
                    Text(text = "Previous")
                }

                Text(text = "Month: ${calendarMonth.month}")

                Button(onClick = {
                    calendarViewState.smoothScrollToMonth(
                        calendarMonth.yearMonth.plusMonths(1)
                    )
                }) {
                    Text(text = "Next")
                }
            }
        },
    )
}

I’m not sure about the notification of view about the changes. This code seems to work fine and all of the views gets updated just because the dayContent composable is listening to selectedDate state. So i need to do some more testing.

I could use some helping hand in testing this in a wild!

Read more comments on GitHub >

github_iconTop Results From Across the Web

Compose layout basics - Android Developers
This document focuses on the layout of elements, explaining some of the building blocks Compose provides to help you lay out your UI...
Read more >
chrisipa/docker-compose-wrapper - GitHub
docker-compose-wrapper. Overview. Wrapper for script for docker-compose adding some very useful convenience commands: Convenience commands (parameters ...
Read more >
Wrap Compose - IntelliJ IDEs Plugin - JetBrains Marketplace
The Wrap Composable plugin allows you to easily wrap your Composable functions. Currently available to wrap with these: Row. Column. Box.
Read more >
react-compose-wrappers - npm
react-compose-wrappers. TypeScript icon, indicating that this package has built-in type declarations. 0.1.0 • Public • Published 2 years ago.
Read more >
Compose Previews in app modules fail to find resources in ...
Note: when putting your compose/preview into a library module the previews do work ... setContent(Wrapper.android.kt:131) at androidx.compose.ui.platform.
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found