I'm working on floating window on android and i'm having a problem.
So I have my activity that calls my floating class that creates a the view and adds it to the windowmanager with a timer that is displayed and counting down. When trying the same code in a normal activity it works find :S?
Here is my viewmodel holding my mutableStateOf value
class WicViewModel : ViewModel() {
private val TAG = "WicViewModel"
private val a = 10000L
private val b = 1000L
private var _input: Long by mutableStateOf(a)
var input: Long
get() = _input
set(value) {
if (value == _input) return
_input = value
}
fun start() {
val timer = object : CountDownTimer(a, b) {
override fun onTick(millisUntilFinished: Long) {
Log.d("MYLOG", "text updated programmatically")
input = millisUntilFinished
}
override fun onFinish() {
input = 0
}
}
timer.start()
}
}
Here is my class with the compose into windowmanager
class WicController constructor(val context: Context?, val viewModel: WicViewModel) {
private val TAG = "WicController"
private var windowManager: WindowManager? = null
private var wicView: View? = null
private lateinit var lifecycleOwner: CDOLifecycleOwner
fun show() {
if (context == null) return
val paramFloat = WindowManager.LayoutParams(
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.WRAP_CONTENT,
getWindowType(),
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
PixelFormat.TRANSLUCENT
)
paramFloat.gravity = Gravity.CENTER or Gravity.END
windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager?
val composeView = ComposeView(context.applicationContext)
composeView.setContent {
timerText(viewModel)
}
lifecycleOwner = CDOLifecycleOwner()
lifecycleOwner.performRestore(null)
lifecycleOwner.handleLifecycleEvent(Lifecycle.Event.ON_CREATE)
ViewTreeLifecycleOwner.set(composeView, lifecycleOwner)
ViewTreeSavedStateRegistryOwner.set(composeView, lifecycleOwner)
windowManager?.addView(composeView, paramFloat)
}
fun hide() {
windowManager?.removeView(wicView!!)
context?.let { Demo.startCalldorado(it) }
}
private fun getWindowType(): Int {
var windowType = WindowManager.LayoutParams.TYPE_SYSTEM_ERROR
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
windowType = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
}
return windowType
}
#Composable
fun timerText(viewModel: WicViewModel){
Text(
text = (viewModel.input / 1000).toString(),
modifier = Modifier
.padding(bottom = 8.dp)
.fillMaxWidth(),
fontSize = 13.sp,
color = Color.White,
)
}}
I know that the value is being changed but the compose is not recomposing itself
sometimes it may change but it very random.
I dont know if im doing it wrong or if compose is not made for windowmanager/floating window use, however I hope some of you guys on here can help me out since I cant find anything on the web about this.
Related
ViewModel does not preserve state during configuration changes, e.g., leaving and returning to the app when switching between background apps.
#HiltViewModel
class MainViewModel #Inject constructor(
private val savedStateHandle: SavedStateHandle,
) : ViewModel()
{
var title by mutableStateOf("")
internal set
var showMenu by mutableStateOf(false)
internal set
var tosVisible by mutableStateOf(false)
internal set
}
The menu:
Currently: It survives the rotation configuration change, the menu remains open if opened by clicking on the three ... dots there. But, changing app, i.e. leaving the app and going into another app. Then returning, does not preserve the state as expected. What am I possibly doing wrong here?
In MainActivity:
val mainViewModel by viewModels<MainViewModel>()
Main(mainViewModel) // Passing it here
#OptIn(ExperimentalMaterial3Api::class)
#Composable
fun Main(viewModel: MainViewModel = viewModel()) {
val context = LocalContext.current
val navController = rememberNavController()
EDIT: Modified my ViewModel to this, Makes no difference.
#HiltViewModel
class MainViewModel #Inject constructor(
private val savedStateHandle: SavedStateHandle,
) : ViewModel()
{
var title by mutableStateOf("")
internal set
var showMenu by mutableStateOf(savedStateHandle["MenuOpenState"] ?: false)
internal set
var tosVisible by mutableStateOf(savedStateHandle["AboutDialogState"] ?: false)
internal set
fun displayAboutDialog(){
savedStateHandle["AboutDialogState"] = tosVisible;
}
fun openMainMenu(){
savedStateHandle["MenuOpenState"] = showMenu;
}
}
Full code of the MainActivity:
#OptIn(ExperimentalMaterial3Api::class)
#Composable
fun Main(viewModel: MainViewModel) {
val context = LocalContext.current
val navController = rememberNavController()
//val scope = rememberCoroutineScope()
val decayAnimationSpec = rememberSplineBasedDecay<Float>()
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior(
decayAnimationSpec,
rememberTopAppBarScrollState()
)
LaunchedEffect(navController){
navController.currentBackStackEntryFlow.collect{backStackEntry ->
Log.d("App", backStackEntry.destination.route.toString())
viewModel.title = getTitleByRoute(context, backStackEntry.destination.route);
}
}
Scaffold(
topBar = {
CenterAlignedTopAppBar(
title = {
Text(
viewModel.title,
//color = Color(0xFF1877F2),
style = MaterialTheme.typography.headlineSmall,
)
},
colors = TopAppBarDefaults.smallTopAppBarColors(
containerColor = MaterialTheme.colorScheme.background
),
actions = {
IconButton(
onClick = {
viewModel.showMenu = !viewModel.showMenu
}) {
Icon(imageVector = Icons.Outlined.MoreVert, contentDescription = "")
MaterialTheme(shapes = MaterialTheme.shapes.copy(extraSmall = RoundedCornerShape(20.dp))) {
IconButton(
onClick = { viewModel.showMenu = !viewModel.showMenu }) {
Icon(imageVector = Icons.Outlined.MoreVert, contentDescription = "")
DropdownMenu(
expanded = viewModel.showMenu,
onDismissRequest = { viewModel.showMenu = false },
modifier = Modifier
.background(MaterialTheme.colorScheme.background)
.padding(0.dp),
properties = PopupProperties(focusable = true)
) {
DropdownMenuItem(text = { Text("Sign out", fontSize = 16.sp) }, onClick = { viewModel.showMenu = false })
DropdownMenuItem(text = { Text("Settings", fontSize = 16.sp) }, onClick = { viewModel.showMenu = false })
Divider(color = Color.LightGray, thickness = 1.dp)
DropdownMenuItem(text = { Text("About", fontSize = 16.sp) },
onClick = {
viewModel.showMenu = true
viewModel.tosVisible = true
})
}
}
}
}
},
scrollBehavior = scrollBehavior
) },
bottomBar = { BottomAppBar(navHostController = navController) }
) { innerPadding ->
Box(modifier = Modifier.padding(PaddingValues(0.dp, innerPadding.calculateTopPadding(), 0.dp, innerPadding.calculateBottomPadding()))) {
BottomNavigationGraph(navController = navController)
}
}
}
Solved with this:
#HiltViewModel
class MainViewModel #Inject constructor(
private val savedStateHandle: SavedStateHandle,
) : ViewModel()
{
companion object {
const val UI_MENU_STATE = "ui.menu.state"
}
init {
savedStateHandle.get<Boolean>(UI_MENU_STATE)?.let {
m -> onMenuStateChange(m);
}
}
private var m_title by mutableStateOf("")
private var displayMenu by mutableStateOf( false)
var tosVisible by mutableStateOf( false)
internal set
fun updateTitleState(title: String){
m_title = title;
}
fun onMenuStateChange(open: Boolean){
Log.d("App", open.toString());
displayMenu = open;
savedStateHandle.set<Boolean>(UI_MENU_STATE, displayMenu);
}
fun isMenuOpen(): Boolean {
return displayMenu;
}
fun getTitle(): String { return m_title; }
}
Maybe you are trying to access another instance of the view model or creating a new instance of the view model upon re-opening the app. The code should work fine if you are accessing the same instance.
Edit:
Seems like you were in fact reinitializing the ViewModel. You are creating a new instance of ViewModel by putting the ViewModel inside the constructor because when you re-open the app the composable will be created again, thus creating a new ViewModel instance. What I will suggest you do is initialise the ViewModel inside composable like this maybe:
fun Main(){
val viewModel = ViewModelProvider(this#MainActivity)[MainViewModel::class.java]
}
I am struggling to understand what is the best way to get this to work.
I have some input fields and I created a TextFieldState to keep all the state in one place.
But it is not triggering a re-composition of the composable so the state never updates.
I saw this stack overflow answer on a similar question, but I just find it confusing and it doesn't make sense to me
Here is the code:
The Composable:
#Composable
fun AddTrip (
addTripVm: AddTripVm = hiltViewModel()
) {
var name = addTripVm.getNameState()
var stateTest = addTripVm.getStateTest()
Column(
//verticalArrangement = Arrangement.Center,
modifier = Modifier
.fillMaxSize()
) {
Text(text = "Add Trip")
Column(
){
println("From Composable: ${name.value.value}") //No Recomposition
meTextField(
value = name.value.value,
onChange = {
addTripVm.updateName(it)
},
placeholder = "Name",
)
}
View Model code:
#HiltViewModel
class AddTripVm #Inject constructor(
private val tripRepository: TripRepositoryContract,
private val tripValidator: TripValidatorContract
): TripValidatorContract by tripValidator, ViewModel() {
/**
* Name of the trip, this is required
*/
private val nameState: MutableState<TextFieldState> = mutableStateOf(TextFieldState())
private var stateTest = mutableStateOf("");
fun updateStateTest(newValue: String) {
stateTest.value = newValue
}
fun getStateTest(): MutableState<String> {
return stateTest
}
fun getNameState(): MutableState<TextFieldState> {
return nameState;
}
fun updateName(name: String) {
println("From ViewModel? $name")
nameState.value.value = name
println("From ViewModel after update: ${nameState.value.value}") //Updates perfectly
}
}
Text field state:
data class TextFieldState(
var value: String = "",
var isValid: Boolean? = null,
var errorMessage: String? = null
)
Is this possible? Or do I need to separate the value as a string and keep the state separate for if its valid or not?
You don't change instance of nameState's value with
nameState.value.value = name
It's the same object which State checks by default with
fun <T> structuralEqualityPolicy(): SnapshotMutationPolicy<T> =
StructuralEqualityPolicy as SnapshotMutationPolicy<T>
private object StructuralEqualityPolicy : SnapshotMutationPolicy<Any?> {
override fun equivalent(a: Any?, b: Any?) = a == b
override fun toString() = "StructuralEqualityPolicy"
}
MutableState use this as
fun <T> mutableStateOf(
value: T,
policy: SnapshotMutationPolicy<T> = structuralEqualityPolicy()
): MutableState<T> = createSnapshotMutableState(value, policy)
Easiest way is to set
nameState.value = nameState.value.copy(value= name)
other option is to write your own SnapshotMutationPolicy
I have top tab view. Each tab returns same composable that shows list of different posts. Initially i fetch tabs and data initial data for each tab. It looks like this:
#Composable
fun Feed() {
val tabsViewModel: TabsViewModel = hiltViewModel()
val tabs = tabsViewModel.state.collectAsState()
val communityFieldViewModel: CommunityFeedViewModel = hiltViewModel()
val selectedTab = tabsViewModel.selectedTab.collectAsState()
val indexOfTile = tabs.value.indexOfFirst { it.id == selectedTab.value }
if(tabs.value.isNotEmpty()) {
var tabIndex by remember { mutableStateOf(indexOfTile) }
Column {
TabRow(selectedTabIndex = tabIndex) {
tabs.value.forEachIndexed { index, tab ->
communityFieldViewModel.loadInitialState(tab.id, tab.tiles, "succeed", tab.token)
Tab(selected = tabIndex == index,
onClick = { tabIndex = index },
text = { Text(text = tab.title) })
}
}
tabs.value.forEachIndexed { _, tab ->
CommunityFeed(tab.id)
}
}
}
}
My CommunityFeed is getting data from CommunityFeedViewModel and I set initial data for CommunityFeedViewModel in Feed. But problem is that I do not know how to get different data in CommunityFeed and now both render for example all posts and should be different. Here is CommunityFeed and CommunityFeedModel. Any advice?
data class CommunityState(
val path: String = "",
val tiles: List<Tile> = emptyList(),
val loadingState: String = "idle",
val token: String? = null
)
#HiltViewModel
class CommunityFeedViewModel #Inject constructor(
private val tileRepository: TileRepository,
): ViewModel() {
private val state = MutableStateFlow(CommunityState())
val modelState = state.asStateFlow()
fun loadInitialState(path: String, tiles: List<Tile>?, loadingState: String, token: String?) {
val tileList = tiles ?: emptyList()
state.value = CommunityState(path, tileList, loadingState, token)
}
}
#Composable
fun CommunityFeed(path: String) {
val feedViewModel: CommunityFeedViewModel = hiltViewModel()
val state = feedViewModel.modelState.collectAsState()
if(state.value.path == path) {
TileListView(tiles = state.value.tiles, loadMore = {})
}
}
I have a complicated layout that caused me to create some custom components. The app requires that I need to add number of rooms, and in each, select number of adults and number of children per room. For each room, I need to allow multiple children per room, but for each child under 18 i need to know their age from a spinner. I can add and remove rooms and add/remove children all day long. In each room I can add children and an age dropdown for each child and remove them. All that works no problem. I get the error when I try to tap any of the age selector dropdowns. I suspect its something to do with context and activity, but not sure where.
The layout looks like this:
I have a fragment controlling all of this, it controls the room stepper and adds/removes rooms.
HotelSearchFragment
class SearchHotelsFragment : Fragment(), StepperView.StepperListener, CustomCalendarView.DayClickListener {
private var listener: OnSearchUpdateListener? = null
private lateinit var mViewModel: HotelRepositoryViewModel
private lateinit var mBinding: com.lixar.allegiant.databinding.FragmentSearchHotelsBinding
private lateinit var mRoomList: MutableList<HotelSearchRoomInput>
private lateinit var mRoomMgrList: MutableList<RoomGuestInputManager>
private var mRoomCount = 1;
interface OnSearchUpdateListener {
fun onSearchUpdate()
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mViewModel = activity?.let { ViewModelProviders.of(it).get(HotelRepositoryViewModel::class.java) }!!
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
mBinding = DataBindingUtil.inflate(inflater, R.layout.fragment_search_hotels, container, false)
mBinding.handler = clickHandler
mBinding.roomStepper.setCallback(this)
setupInitialRoom()
hideKeyboard()
return mBinding.root
}
public fun getSearchCriteria(): HotelSearchInput {
val mRoomData = HotelSearchInput.builder()
.locationCode("")
.from(mViewModel.fromDateLocal)
.to(mViewModel.toDateLocal)
.rooms(mRoomList)
.build()
return mRoomData
}
private fun setupInitialRoom() {
val roomMgr = activity?.applicationContext?.let { RoomGuestInputManager(it) }
roomMgr?.setActivity(activity!!)
roomMgr?.setRoomNumber(mRoomCount)
mBinding.roomExpansionZone.addView(roomMgr)
mRoomMgrList = listOf(roomMgr!!).toMutableList()
mRoomList = listOf(roomMgr.mRoom!!).toMutableList()
}
override fun onDecrement(id: Int, count: Int) {
when (id) {
R.id.room_stepper -> {
val lastRoomMgr = mRoomMgrList.get(mRoomCount-1)
mBinding.roomExpansionZone.removeView(lastRoomMgr)
mRoomMgrList.remove(lastRoomMgr)
mRoomList.remove(lastRoomMgr.mRoom)
mRoomCount--
// once we have ONLY one room grow the left side to hider room numbers
if (mRoomCount == 1) {
val room = mRoomMgrList.get(0)
room.adjustRoomOnRemoval()
}
}
}
}
override fun onIncrement(id: Int, count: Int) {
when (id) {
R.id.room_stepper -> {
val roomMgr = activity?.applicationContext?.let { RoomGuestInputManager(it) }
roomMgr?.setActivity(activity!!)
mRoomCount++
roomMgr?.setRoomNumber(mRoomCount)
mBinding.roomExpansionZone.addView(roomMgr)
mRoomList.add(roomMgr?.mRoom!!)
mRoomMgrList.add(roomMgr)
// once we have more than one room shrink the left side to allow for room numbers
if (mRoomCount > 1) {
val room = mRoomMgrList.get(0)
room.adjustRoomOnAddition()
}
}
}
}
}
Within that fragment, I've incorporated a custom component, RoomGuestInputManager. It's job is to add "rooms", with an adult stepper and child stepper or remove them
RoomGuestInputManager
class RoomGuestInputManager #JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : LinearLayout(context, attrs, defStyleAttr), StepperView.StepperListener {
val FULLSIZE_LEFT = 0.0f
val FULLSIZE_RIGHT = 0.5f
val SIZE_LEFT = 0.2f
val SIZE_RIGHT = 0.6f
val mRoom = HotelSearchRoomInput.builder()
.adults(1) // there must be at least one adult per room - sorry kids
.childrenAges(listOf(0))
.build()
private lateinit var mBinding: RoomSelectLayoutBinding
private var mNumChildren = 0
private var mNumChildMgrs = 0
private var mActivity: Activity? = null
private lateinit var mChildMgrList: MutableList<RoomChildManager>
init {
mBinding = DataBindingUtil.inflate(LayoutInflater.from(context), R.layout.room_select_layout, this, true)
mBinding.adultStepper.setCallback(this)
mBinding.childStepper.setCallback(this)
mBinding.roomNumber.visibility = View.GONE
}
public fun setRoomNumber(room: Int) {
val label = context.resources.getString(R.string.room_num)
if (room == 1) {
mBinding.roomNumber.visibility = View.GONE
mBinding.guidelineleft.setGuidelinePercent(FULLSIZE_LEFT)
mBinding.guidelineright.setGuidelinePercent(FULLSIZE_RIGHT)
} else {
mBinding.roomNumber.visibility = View.VISIBLE
mBinding.roomNumber.text = String.format(label, room)
mBinding.guidelineleft.setGuidelinePercent(SIZE_LEFT)
mBinding.guidelineright.setGuidelinePercent(SIZE_RIGHT)
}
}
public fun setActivity(activity: Activity) {
mActivity = activity
}
public fun adjustRoomOnAddition() {
mBinding.roomNumber.visibility = View.VISIBLE
mBinding.guidelineleft.setGuidelinePercent(SIZE_LEFT)
mBinding.guidelineright.setGuidelinePercent(SIZE_RIGHT)
}
public fun adjustRoomOnRemoval() {
mBinding.roomNumber.visibility = View.GONE
mBinding.guidelineleft.setGuidelinePercent(FULLSIZE_LEFT)
mBinding.guidelineright.setGuidelinePercent(FULLSIZE_RIGHT)
}
public fun getGuestInfoThisRoom(): HotelSearchRoomInput {
return mRoom
}
private fun setupNewChild() {
val childMgr = RoomChildManager(mActivity?.applicationContext!!)
mBinding.childExpansionZone.addView(childMgr)
childMgr.setInitialChild(mNumChildren)
if (mNumChildren == 1) {
mChildMgrList = mutableListOf(childMgr)
} else {
mChildMgrList.add(childMgr)
}
mNumChildMgrs++
}
override fun onDecrement(id: Int, count: Int) {
when (id) {
R.id.adult_stepper -> {
mRoom.adults().minus(1)
}
R.id.child_stepper -> {
// depending on how many kids there are now, do we remove a layout or just make gone?
if (mNumChildren == 1) {
mBinding.childExpansionZone.removeAllViews()
mChildMgrList.clear()
mNumChildren = 0
} else if (mNumChildren.rem(2) == 0) {
// remove the secondary ageSelector
val childMgr = mChildMgrList.get(mNumChildMgrs - 1)
childMgr.removeChild()
mNumChildren--
} else {
// remove the entire 2-selector layout
val childMgr = mChildMgrList.get(mNumChildMgrs - 1)
childMgr.removeChild()
mChildMgrList.removeAt(mNumChildMgrs - 1)
mBinding.childExpansionZone.removeView(childMgr)
mNumChildMgrs--
mNumChildren--
}
}
}
}
override fun onIncrement(id: Int, count: Int) {
when (id) {
R.id.adult_stepper -> {
mRoom.adults().plus(1)
}
R.id.child_stepper -> {
// depending on how many kids there are now, do we add a layout
if (mNumChildren == 0) {
mNumChildren = 1
setupNewChild()
} else if (mNumChildren.rem(2) == 0) {
mNumChildren++
setupNewChild()
} else {
// expose 2nd selector
val childMgr = mChildMgrList.get(mNumChildMgrs - 1)
mNumChildren++
childMgr.addChild(mNumChildren)
}
}
}
}
That class controls a second custom component, RoomChildManager. It's job is to manage the age selectors for each room and make sure we have an age for each child. Its adds/removes the dropdown selectors in pairs to satisfy the design that when touched, give me the error in the title:
android.view.WindowManager$BadTokenException: Unable to add window -- token null is not valid; is your activity running?
RoomChildManager
class RoomChildManager #JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : LinearLayout(context, attrs, defStyleAttr), DropdownAgeSelectView.AgeSelectListener {
private lateinit var mBinding: ChildAgeselectLayoutBinding
init {
mBinding = DataBindingUtil.inflate(LayoutInflater.from(context), R.layout.child_ageselect_layout, this, true)
mBinding.child2Age.visibility = View.GONE
mBinding.child2Num.visibility = View.GONE
mBinding.child1Age.setCallback(this)
mBinding.child2Age.setCallback(this)
}
public fun setInitialChild(num: Int) {
mBinding.child1Num.text = String.format(context.resources.getString(R.string.child_age), num)
}
public fun addChild(num: Int) {
mBinding.child2Age.visibility = View.VISIBLE
mBinding.child2Num.visibility = View.VISIBLE
mBinding.child2Num.text = String.format(context.resources.getString(R.string.child_age), num)
}
public fun removeChild() {
mBinding.child2Age.visibility = View.GONE
mBinding.child2Num.visibility = View.GONE
}
public fun getChildAges(): List<Int> {
return listOf(mBinding.child1Age.getSelection(), mBinding.child2Age.getSelection())
}
override fun onAgeSelect(id: Int, age: Int) {
when (id) {
R.id.child1_age -> {
// do something
}
R.id.child2_age -> {
// do something
}
}
}
}
Any ideas why touching any dropdown gives me the error?
I knew this was going to be a context issue, just couldn't tell from the error messages exactly where it was hitting.
It turns out, that the way to solve this issue lies in the way context was passed from the HotelSearchFragment to the RoomGuestInputManager. I was assuming the context I could get a fragment was good enough. Nope, apparently, they get confused, so I had to spell out exactly where the activity is. In HotelSearchFragment, we set that as:
val hotelActivity = context as HotelSelectActivity
then we can invoke RoomInputGuestManager like this:
val roomMgr = RoomGuestInputManager(hotelActivity)
and also inside RoomInputGuestManager when we create the Child manager as
val hotelActivity = context as HotelSelectActivity
val childMgr = RoomChildManager(hotelActivity )
After that, everything works fine.
In hopes to better understand MVVM I decided to redo one of my Android projects implementing the architecture.
One of the activities uses the Zxing library to scan a QR code. Using Zxing's event handler I would like to pass the scanned result to my viewModel to check for example if the result is a "product"(this is a QR code I generate following a product class) or not. If the the result is a product it will show a dialogue with the result asking the user if they want to continue scanning or save the product, if it is not a product then it shows a dialogue with the result and a cancel button.
Now the issue here is that I don't know how to properly pass the data from Zxing's handler to the viewModel. Below attached my activity.
class QRreader_Activity : AppCompatActivity(), ZXingScannerView.ResultHandler, IQRreader{
override fun isProduct(ProductDetail: String) {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun isNotProduct(ScannedText: String) {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
lateinit var mScannerView: ZXingScannerView
var data: String="test"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_qrreader)
val bReturn: ImageButton = findViewById(R.id.ic_return)
bReturn.setOnClickListener {
onBackPressed() }
ActivityCompat.requestPermissions(this#QRreader_Activity,
arrayOf(Manifest.permission.CAMERA),
1)
val flashlightCheckBox = findViewById<CheckBox>(R.id.flashlight_checkbox)
val contentFrame = findViewById<ViewGroup>(R.id.content_frame)
mScannerView = object : ZXingScannerView(this) {
override fun createViewFinderView(context: Context): IViewFinder {
return CustomViewFinderView(context)
}
}
contentFrame.addView(mScannerView)
flashlightCheckBox.setOnCheckedChangeListener { compoundButton, isChecked -> mScannerView.flash = isChecked }
}
public override fun onResume() {
super.onResume()
mScannerView.setResultHandler(this) // Register ourselves as a handler for scan results.
mScannerView.startCamera() // Start camera on resume
}
public override fun onPause() {
super.onPause()
mScannerView.stopCamera() // Stop camera on pause
}
override fun onBackPressed() {
super.onBackPressed()
val intent = Intent(this, MainActivity::class.java)
startActivity(intent)
finish()
}
//This is where ZXing provides the result of the scan
override fun handleResult(rawResult: Result) {
Toast.makeText(this,""+rawResult.text,Toast.LENGTH_LONG).show()
}
private class CustomViewFinderView : ViewFinderView {
val PAINT = Paint()
constructor(context: Context) : super(context) {
init()
}
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
init()
}
private fun init() {
PAINT.color = Color.WHITE
PAINT.isAntiAlias = true
val textPixelSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,
TRADE_MARK_TEXT_SIZE_SP.toFloat(), resources.displayMetrics)
PAINT.textSize = textPixelSize
setSquareViewFinder(true)
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
drawTradeMark(canvas)
}
private fun drawTradeMark(canvas: Canvas) {
val framingRect = framingRect
val tradeMarkTop: Float
val tradeMarkLeft: Float
if (framingRect != null) {
tradeMarkTop = framingRect.bottom.toFloat() + PAINT.textSize + 10f
tradeMarkLeft = framingRect.left.toFloat()
} else {
tradeMarkTop = 10f
tradeMarkLeft = canvas.height.toFloat() - PAINT.textSize - 10f
}
canvas.drawText(TRADE_MARK_TEXT, tradeMarkLeft, tradeMarkTop, PAINT)
}
companion object {
val TRADE_MARK_TEXT = ""
val TRADE_MARK_TEXT_SIZE_SP = 40
}
}
}
The solution I found was to have the view model implement "ZXingScannerView.ResultHandler" interface. I don't know if this is the optimal solution though.