Skip to content

Commit

Permalink
Remove runBlocking and other async improvements to CameraXBasic.
Browse files Browse the repository at this point in the history
  • Loading branch information
donovanfm committed Jan 5, 2023
1 parent 40bd073 commit 0c9647d
Show file tree
Hide file tree
Showing 2 changed files with 53 additions and 36 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@ import com.android.example.cameraxbasic.utils.MediaStoreUtils
import com.android.example.cameraxbasic.utils.simulateClick
import com.bumptech.glide.Glide
import com.bumptech.glide.request.RequestOptions
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.nio.ByteBuffer
import java.text.SimpleDateFormat
Expand Down Expand Up @@ -457,7 +456,7 @@ class CameraFragment : Fragment() {
)

// In the background, load latest photo taken (if any) for gallery thumbnail
lifecycleScope.launch(Dispatchers.IO) {
lifecycleScope.launch {
val thumbnailUri = mediaStoreUtils.getLatestImageFilename()
thumbnailUri?.let {
setGalleryThumbnail(it)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import android.webkit.MimeTypeMap
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import androidx.lifecycle.lifecycleScope
import androidx.navigation.Navigation
import androidx.navigation.fragment.navArgs
import androidx.viewpager2.adapter.FragmentStateAdapter
Expand All @@ -36,7 +37,8 @@ import com.android.example.cameraxbasic.utils.MediaStoreFile
import com.android.example.cameraxbasic.utils.MediaStoreUtils
import com.android.example.cameraxbasic.utils.padWithDisplayCutout
import com.android.example.cameraxbasic.utils.showImmersive
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.launch

/** Fragment used to present the user with a gallery of photos taken */
class GalleryFragment internal constructor() : Fragment() {
Expand All @@ -49,27 +51,37 @@ class GalleryFragment internal constructor() : Fragment() {
/** AndroidX navigation arguments */
private val args: GalleryFragmentArgs by navArgs()

private lateinit var mediaList: MutableList<MediaStoreFile>
private var mediaList: MutableList<MediaStoreFile> = mutableListOf()
private var hasMediaItems = CompletableDeferred<Boolean>()

/** Adapter class used to present a fragment containing one photo or video as a page */
inner class MediaPagerAdapter(fm: FragmentManager) : FragmentStateAdapter(fm, lifecycle) {
inner class MediaPagerAdapter(fm: FragmentManager,
private var mediaList: MutableList<MediaStoreFile>) :
FragmentStateAdapter(fm, lifecycle) {
override fun getItemCount(): Int = mediaList.size
override fun createFragment(position: Int): Fragment = PhotoFragment.create(mediaList[position])
override fun createFragment(position: Int): Fragment =
PhotoFragment.create(mediaList[position])
override fun getItemId(position: Int): Long {
return mediaList[position].id
}
override fun containsItem(itemId: Long): Boolean {
return null != mediaList.firstOrNull { it.id == itemId }
}
fun setMediaListAndNotify(mediaList: MutableList<MediaStoreFile>) {
this.mediaList = mediaList
notifyDataSetChanged()
}
}

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

runBlocking {
lifecycleScope.launch {
// Get images this app has access to from MediaStore
val mediaStoreUtils = MediaStoreUtils(requireContext())
mediaList = mediaStoreUtils.getImages()
mediaList = MediaStoreUtils(requireContext()).getImages()
(fragmentGalleryBinding.photoViewPager.adapter as MediaPagerAdapter)
.setMediaListAndNotify(mediaList)
hasMediaItems.complete(mediaList.isNotEmpty())
}
}

Expand All @@ -85,16 +97,15 @@ class GalleryFragment internal constructor() : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)

//Checking media files list
if (mediaList.isEmpty()) {
fragmentGalleryBinding.deleteButton.isEnabled = false
fragmentGalleryBinding.shareButton.isEnabled = false
lifecycleScope.launch {
fragmentGalleryBinding.deleteButton.isEnabled = hasMediaItems.await()
fragmentGalleryBinding.shareButton.isEnabled = hasMediaItems.await()
}

// Populate the ViewPager and implement a cache of two media items
fragmentGalleryBinding.photoViewPager.apply {
offscreenPageLimit = 2
adapter = MediaPagerAdapter(childFragmentManager)
adapter = MediaPagerAdapter(childFragmentManager, mediaList)
}

// Make sure that the cutout "safe area" avoids the screen notch if any
Expand All @@ -105,38 +116,41 @@ class GalleryFragment internal constructor() : Fragment() {

// Handle back button press
fragmentGalleryBinding.backButton.setOnClickListener {
Navigation.findNavController(requireActivity(), R.id.fragment_container).navigateUp()
Navigation.findNavController(requireActivity(), R.id.fragment_container)
.navigateUp()
}

// Handle share button press
fragmentGalleryBinding.shareButton.setOnClickListener {

mediaList.getOrNull(fragmentGalleryBinding.photoViewPager.currentItem)?.let { mediaStoreFile ->
val mediaFile = mediaStoreFile.file
// Create a sharing intent
val intent = Intent().apply {
// Infer media type from file extension
val mediaType = MimeTypeMap.getSingleton()
mediaList.getOrNull(fragmentGalleryBinding.photoViewPager.currentItem)
?.let { mediaStoreFile ->
val mediaFile = mediaStoreFile.file
// Create a sharing intent
val intent = Intent().apply {
// Infer media type from file extension
val mediaType = MimeTypeMap.getSingleton()
.getMimeTypeFromExtension(mediaFile.extension)
// Set the appropriate intent extra, type, action and flags
type = mediaType
action = Intent.ACTION_SEND
flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
putExtra(Intent.EXTRA_STREAM, mediaStoreFile.uri)
// Set the appropriate intent extra, type, action and flags
type = mediaType
action = Intent.ACTION_SEND
flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
putExtra(Intent.EXTRA_STREAM, mediaStoreFile.uri)
}

// Launch the intent letting the user choose which app to share with
startActivity(Intent.createChooser(intent, getString(R.string.share_hint)))
}

// Launch the intent letting the user choose which app to share with
startActivity(Intent.createChooser(intent, getString(R.string.share_hint)))
}
}

// Handle delete button press
fragmentGalleryBinding.deleteButton.setOnClickListener {

mediaList.getOrNull(fragmentGalleryBinding.photoViewPager.currentItem)?.let { mediaStoreFile ->
val mediaFile = mediaStoreFile.file
mediaList.getOrNull(fragmentGalleryBinding.photoViewPager.currentItem)
?.let { mediaStoreFile ->
val mediaFile = mediaStoreFile.file

AlertDialog.Builder(view.context, android.R.style.Theme_Material_Dialog)
AlertDialog.Builder(view.context, android.R.style.Theme_Material_Dialog)
.setTitle(getString(R.string.delete_title))
.setMessage(getString(R.string.delete_dialog))
.setIcon(android.R.drawable.ic_dialog_alert)
Expand All @@ -147,22 +161,26 @@ class GalleryFragment internal constructor() : Fragment() {

// Send relevant broadcast to notify other apps of deletion
MediaScannerConnection.scanFile(
view.context, arrayOf(mediaFile.absolutePath), null, null)
view.context, arrayOf(mediaFile.absolutePath), null, null
)

// Notify our view pager
mediaList.removeAt(fragmentGalleryBinding.photoViewPager.currentItem)
fragmentGalleryBinding.photoViewPager.adapter?.notifyDataSetChanged()

// If all photos have been deleted, return to camera
if (mediaList.isEmpty()) {
Navigation.findNavController(requireActivity(), R.id.fragment_container).navigateUp()
Navigation.findNavController(
requireActivity(),
R.id.fragment_container
).navigateUp()
}

}

.setNegativeButton(android.R.string.cancel, null)
.create().showImmersive()
}
}
}
}

Expand Down

0 comments on commit 0c9647d

Please sign in to comment.