I have never tried this before. Just started with Kotlin and Android CameraX. Here is a fragment I would like some feedback on so I can writer safer and better Kotlin code.
package com.name.app.maincontroller
import android.Manifest
import android.annotation.SuppressLint
import android.content.Context
import android.content.pm.PackageManager
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageButton
import androidx.camera.core.*
import androidx.camera.lifecycle.ProcessCameraProvider
import androidx.camera.view.PreviewView
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment
import androidx.lifecycle.LifecycleOwner
import com.name.app.persistentdata.getOutputDirectory
import java.io.*
import java.text.SimpleDateFormat
import java.util.*
import java.util.concurrent.Executors
import kotlin.random.Random
const val height: Int = 175
const val width: Int = 100
class CameraActivityFragment : Fragment() {
private val permissions = listOf(Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO)
private val permissionsRequestCode = Random.nextInt(0, 10000)
private var isRecording = false
private val videoCapture = VideoCapture.Builder().build()
private lateinit var cameraCaptureButton: ImageButton
private lateinit var cameraPreview: PreviewView
private lateinit var cameraProvider: ProcessCameraProvider
private lateinit var preview: Preview
private val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA
private fun setRecordVideoListener() {
// Disable all camera controls
cameraCaptureButton.setOnClickListener {
it.isEnabled = false
if (!isRecording) {
isRecording = true
cameraCaptureButton.setBackgroundResource(R.drawable.ic_shutter_pressed)
it.invalidate()
startRecording()
} else {
isRecording = false
cameraCaptureButton.setBackgroundResource(R.drawable.ic_shutter_normal)
it.invalidate()
stopRecording()
}
// Re-enable camera controls
it.isEnabled = true
}
}
@SuppressLint("RestrictedApi")
private fun startRecording() {
val appName = resources.getString(R.string.app_name)
val currentTime = System.currentTimeMillis()
val videoFile = File(
getOutputDirectory(requireActivity(), appName),
SimpleDateFormat(
"yyyy-MM-dd-HH-mm-ss-SSS", Locale.US
).format(currentTime) + ".mp4"
)
val outputOptions = VideoCapture.OutputFileOptions.Builder(videoFile).build()
if (ActivityCompat.checkSelfPermission(
requireContext(),
Manifest.permission.RECORD_AUDIO
) != PackageManager.PERMISSION_GRANTED
) {
return
}
videoCapture.startRecording(outputOptions, ContextCompat.getMainExecutor(activity),
object : VideoCapture.OnVideoSavedCallback {
override fun onError(videoCaptureError: Int, message: String, cause: Throwable?) {
Log.e(TAG, "Video capture failed: $message")
}
override fun onVideoSaved(outputFileResults: VideoCapture.OutputFileResults) {
}
})
}
@SuppressLint("RestrictedApi")
private fun stopRecording() {
videoCapture.stopRecording()
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
cameraCaptureButton = view.findViewById(R.id.camera_capture_button)
cameraPreview = view.findViewById(R.id.camera_preview)
setRecordVideoListener()
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_camera_activity, container, false)
}
@SuppressLint("UnsafeExperimentalUsageError")
private fun bindVideoCapture() {
// Create a new camera selector each time, enforcing lens facing
try {
// Apply declared configs to CameraX using the same lifecycle owner
cameraProvider.bindToLifecycle(
this as LifecycleOwner, cameraSelector, preview, videoCapture
)
} catch (exc: Exception) {
Log.e("BindVideoCapture", "Use case binding failed", exc)
}
}
private fun startCamera() {
val cameraProviderFuture = ProcessCameraProvider.getInstance(requireContext())
cameraProviderFuture.addListener({
// Used to bind the lifecycle of cameras to the lifecycle owner
cameraProvider = cameraProviderFuture.get()
cameraProvider.unbindAll()
// Set up the view finder use case to display camera preview
preview = Preview.Builder()
.build()
try {
bindVideoCapture()
} catch (exc: Exception) {
Log.e("TestCameraPytorch", "Use case binding failed", exc)
}
preview.setSurfaceProvider(cameraPreview.surfaceProvider)
}, ContextCompat.getMainExecutor(requireContext()))
}
override fun onResume() {
super.onResume()
// Request permissions each time the app resumes, since they can be revoked at any time
if (!context?.let { hasPermissions(it) }!!) {
activity?.let {
ActivityCompat.requestPermissions(
it, permissions.toTypedArray(), permissionsRequestCode
)
}
} else {
startCamera()
}
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == permissionsRequestCode && hasPermissions(requireContext())) {
startCamera()
} else {
activity?.finish() // If we don't have the required permissions, we can't run
}
}
/** Convenience method used to check if all permissions required by this app are granted */
private fun hasPermissions(context: Context) = permissions.all {
ContextCompat.checkSelfPermission(context, it) == PackageManager.PERMISSION_GRANTED
}
companion object {
private val TAG = MainActivity::class.java.simpleName
}
}