How to crop image rectangle in camera preview on CameraX - kotlin

I have a custom camera app which has a centered rectangle view, as you can see below:
When I take a picture I want to ignore everything outside the rectangle. And this is my XML layout:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/black_50">
<TextureView
android:id="#+id/viewFinder"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<View
android:layout_width="match_parent"
android:layout_height="250dp"
android:layout_margin="16dp"
android:background="#drawable/rectangle"
app:layout_constraintBottom_toTopOf="#+id/cameraBottomView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<View
android:id="#+id/cameraBottomView"
android:layout_width="match_parent"
android:layout_height="130dp"
android:background="#color/black_50"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<ImageButton
android:id="#+id/cameraCaptureImageButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#android:color/transparent"
android:src="#drawable/ic_capture_image"
app:layout_constraintBottom_toBottomOf="#id/cameraBottomView"
app:layout_constraintEnd_toEndOf="#id/cameraBottomView"
app:layout_constraintStart_toStartOf="#id/cameraBottomView"
app:layout_constraintTop_toTopOf="#id/cameraBottomView"
tools:ignore="ContentDescription" />
</androidx.constraintlayout.widget.ConstraintLayout>
And this is my kotlin code for the cameraX preview:
class CameraFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_camera, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewFinder.post { setupCamera() }
}
private fun setupCamera() {
CameraX.unbindAll()
CameraX.bindToLifecycle(
this,
buildPreviewUseCase(),
buildImageCaptureUseCase(),
buildImageAnalysisUseCase()
)
}
private fun buildPreviewUseCase(): Preview {
val preview = Preview(
UseCaseConfigBuilder.buildPreviewConfig(
viewFinder.display
)
)
preview.setOnPreviewOutputUpdateListener { previewOutput ->
updateViewFinderWithPreview(previewOutput)
correctPreviewOutputForDisplay(previewOutput.textureSize)
}
return preview
}
private fun updateViewFinderWithPreview(previewOutput: Preview.PreviewOutput) {
val parent = viewFinder.parent as ViewGroup
parent.removeView(viewFinder)
parent.addView(viewFinder, 0)
viewFinder.surfaceTexture = previewOutput.surfaceTexture
}
/**
* Corrects the camera/preview's output to the display, by scaling
* up/down and/or rotating the camera/preview's output.
*/
private fun correctPreviewOutputForDisplay(textureSize: Size) {
val matrix = Matrix()
val centerX = viewFinder.width / 2f
val centerY = viewFinder.height / 2f
val displayRotation = getDisplayRotation()
val (dx, dy) = getDisplayScalingFactors(textureSize)
matrix.postRotate(displayRotation, centerX, centerY)
matrix.preScale(dx, dy, centerX, centerY)
// Correct preview output to account for display rotation and scaling
viewFinder.setTransform(matrix)
}
private fun getDisplayRotation(): Float {
val rotationDegrees = when (viewFinder.display.rotation) {
Surface.ROTATION_0 -> 0
Surface.ROTATION_90 -> 90
Surface.ROTATION_180 -> 180
Surface.ROTATION_270 -> 270
else -> throw IllegalStateException("Unknown display rotation ${viewFinder.display.rotation}")
}
return -rotationDegrees.toFloat()
}
private fun getDisplayScalingFactors(textureSize: Size): Pair<Float, Float> {
val cameraPreviewRation = textureSize.height / textureSize.width.toFloat()
val scaledWidth: Int
val scaledHeight: Int
if (viewFinder.width > viewFinder.height) {
scaledHeight = viewFinder.width
scaledWidth = (viewFinder.width * cameraPreviewRation).toInt()
} else {
scaledHeight = viewFinder.height
scaledWidth = (viewFinder.height * cameraPreviewRation).toInt()
}
val dx = scaledWidth / viewFinder.width.toFloat()
val dy = scaledHeight / viewFinder.height.toFloat()
return Pair(dx, dy)
}
private fun buildImageCaptureUseCase(): ImageCapture {
val capture = ImageCapture(
UseCaseConfigBuilder.buildImageCaptureConfig(
viewFinder.display
)
)
cameraCaptureImageButton.setOnClickListener {
capture.takePicture(
FileCreator.createTempFile(JPEG_FORMAT),
Executors.newSingleThreadExecutor(),
object : ImageCapture.OnImageSavedListener {
override fun onImageSaved(file: File) {
requireActivity().runOnUiThread {
launchGalleryFragment(file.absolutePath)
}
}
override fun onError(
imageCaptureError: ImageCapture.ImageCaptureError,
message: String,
cause: Throwable?
) {
Toast.makeText(requireContext(), "Error: $message", Toast.LENGTH_LONG)
.show()
Log.e("CameraFragment", "Capture error $imageCaptureError: $message", cause)
}
})
}
return capture
}
private fun buildImageAnalysisUseCase(): ImageAnalysis {
val analysis = ImageAnalysis(
UseCaseConfigBuilder.buildImageAnalysisConfig(
viewFinder.display
)
)
analysis.setAnalyzer(
Executors.newSingleThreadExecutor(),
ImageAnalysis.Analyzer { image, rotationDegrees ->
Log.d(
"CameraFragment",
"Image analysis: $image - Rotation degrees: $rotationDegrees"
)
})
return analysis
}
private fun launchGalleryFragment(path: String) {
val action = CameraFragmentDirections.actionLaunchGalleryFragment(path)
findNavController().navigate(action)
}
}
And when I take the picture and send it into new page (GalleryPage), it's show all screen from the camera preview as you can see below:
And this is the kotlin code to get the picture from cameraX preview and display it into ImageView:
class GalleryFragment : Fragment() {
private lateinit var imageView: ImageView
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_gallery, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
imageView = view.findViewById(R.id.img)
val imageFilePath = GalleryFragmentArgs.fromBundle(arguments!!).data
val bitmap = BitmapFactory.decodeFile(imageFilePath)
val rotatedBitmap = bitmap.rotate(90)
if (imageFilePath.isBlank()) {
Log.i(
"GalleryFragment",
"Image is Null or Empty"
)
} else {
Glide.with(activity!!)
.load(rotatedBitmap)
.into(imageView)
}
}
private fun Bitmap.rotate(degree:Int):Bitmap{
// Initialize a new matrix
val matrix = Matrix()
// Rotate the bitmap
matrix.postRotate(degree.toFloat())
// Resize the bitmap
val scaledBitmap = Bitmap.createScaledBitmap(
this,
width,
height,
true
)
// Create and return the rotated bitmap
return Bitmap.createBitmap(
scaledBitmap,
0,
0,
scaledBitmap.width,
scaledBitmap.height,
matrix,
true
)
}
}
Can somebody help me how to crop the image properly? Because I already search and research how to do it but still confused and not working for me.

I found a simple and straight forward way of doing this using camerax configuration.
Get the height and width of your rectangle shape of the preview area that you need from the camera preview.
For example
<View
android:background="#drawable/background_drawable"
android:id="#+id/border_view"
android:layout_gravity="center"
android:layout_width="350dp"
android:layout_height="100dp"/>
The width of mine is 350dp and a height of 100dp
Then use ViewPort to get the area you need
val viewPort = ViewPort.Builder(Rational(width, height), rotation).build()
//width = 350, height = 100, rotation = Surface.ROTATION_0
val useCaseGroup = UseCaseGroup.Builder()
.addUseCase(preview) //your preview
.addUseCase(imageAnalysis) //if you are using imageAnalysis
.addUseCase(imageCapture)
.setViewPort(viewPort)
.build()
Then bind to LifeCycle of CameraProvider
cameraProvider.bindToLifecycle(this, cameraSelector, useCaseGroup)
Use this link CropRect for more information
If you need any help comment below, I can provide you with the working source code.
Edit
Link to Source Code Sample

I have a solution, I just use this function to cropping the Image after capturing the Image:
private fun cropImage(bitmap: Bitmap, frame: View, reference: View): ByteArray {
val heightOriginal = frame.height
val widthOriginal = frame.width
val heightFrame = reference.height
val widthFrame = reference.width
val leftFrame = reference.left
val topFrame = reference.top
val heightReal = bitmap.height
val widthReal = bitmap.width
val widthFinal = widthFrame * widthReal / widthOriginal
val heightFinal = heightFrame * heightReal / heightOriginal
val leftFinal = leftFrame * widthReal / widthOriginal
val topFinal = topFrame * heightReal / heightOriginal
val bitmapFinal = Bitmap.createBitmap(
bitmap,
leftFinal, topFinal, widthFinal, heightFinal
)
val stream = ByteArrayOutputStream()
bitmapFinal.compress(
Bitmap.CompressFormat.JPEG,
100,
stream
) //100 is the best quality possibe
return stream.toByteArray()
}
Crop an image taking a reference a view parent like a frame and a view child like final reference
param bitmap image to crop
param frame where the image is set it
param reference frame to take reference for a crop the image
return image already cropped
You can see this example: https://github.com/rrifafauzikomara/CustomCamera/tree/custom_camerax

Here is an example of how I'm cropping an image taken by cameraX as you mentioned. I don't know if it the best way to do it and I'm interested to know other solutions.
camerax_version = "1.0.0-alpha07"
CameraFragment.java
Initialize cameraX :
// Views
private PreviewView previewView;
// CameraX
private ProcessCameraProvider cameraProvider;
private ListenableFuture<ProcessCameraProvider> cameraProviderFuture;
private CameraSelector cameraSelector;
private Executor executor;
private ImageCapture imageCapture;
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
cameraProviderFuture = ProcessCameraProvider.getInstance(getContext());
executor = ContextCompat.getMainExecutor(getContext());
cameraSelector = new CameraSelector.Builder().requireLensFacing(LensFacing.BACK).build();
}
#Override
public void onViewCreated(#NonNull View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
previewView = view.findViewById(R.id.preview);
ImageButton btnCapture = view.findViewById(R.id.btn_capture);
// Wait for the view to be properly laid out
previewView.post(() ->{
//Initialize CameraX
cameraProviderFuture.addListener(() -> {
if(cameraProvider != null) cameraProvider.unbindAll();
try {
cameraProvider = cameraProviderFuture.get();
// Set up the preview use case to display camera preview
Preview preview = new Preview.Builder()
.setTargetAspectRatio(AspectRatio.RATIO_4_3)
.setTargetRotation(previewView.getDisplay().getRotation())
.build();
preview.setPreviewSurfaceProvider(previewView.getPreviewSurfaceProvider());
// Set up the capture use case to allow users to take photos
imageCapture = new ImageCapture.Builder()
.setCaptureMode(ImageCapture.CaptureMode.MINIMIZE_LATENCY)
.setTargetRotation(previewView.getDisplay().getRotation())
.setTargetAspectRatio(AspectRatio.RATIO_4_3)
.build();
// Apply declared configs to CameraX using the same lifecycle owner
cameraProvider.bindToLifecycle(this, cameraSelector, preview,imageCapture);
} catch (ExecutionException | InterruptedException e) {
e.printStackTrace();
}
}, ContextCompat.getMainExecutor(getContext()));
});
btnCapture.setOnClickListener(v -> {
String format = "yyyy-MM-dd-HH-mm-ss-SSS";
SimpleDateFormat fmt = new SimpleDateFormat(format, Locale.US);
String date = fmt.format(System.currentTimeMillis());
File file = new File(getContext().getCacheDir(), date+".jpg");
imageCapture.takePicture(file, executor, imageSavedListener);
});
}
When a photo has been taken, open the gallery fragment passing the path of the photo :
private ImageCapture.OnImageSavedCallback imageSavedListener = new ImageCapture.OnImageSavedCallback() {
#Override
public void onImageSaved(#NonNull File photoFile) {
// Create new fragment and transaction
Fragment newFragment = new GalleryFragment();
FragmentTransaction transaction = getActivity().getSupportFragmentManager().beginTransaction();
// Set arguments
Bundle args = new Bundle();
args.putString("KEY_PATH", Uri.fromFile(photoFile).toString());
newFragment.setArguments(args);
// Replace whatever is in the fragment_container view with this fragment,
transaction.replace(R.id.fragment_container, newFragment,null);
transaction.addToBackStack(null);
// Commit the transaction
transaction.commit();
}
#Override
public void onError(int imageCaptureError, #NonNull String message, #Nullable Throwable cause) {
if (cause != null) {
cause.printStackTrace();
}
}
};
At this moment, the photo has not been cropped, I don't know if it possible to do it directly with cameraX.
GalleryFragment.java
Load the argument passed to the fragment.
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
String path = getArguments().getString("KEY_PATH");
sourceUri = Uri.parse(path);
}
Load the Uri with glide in an ImageView and then crop it.
#Override
public void onViewCreated(#NonNull View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
// Initialize the views
ImageView imageView = view.findViewById(R.id.image_view);
View cropArea = view.findViewById(R.id.crop_area);
// Display the image
Glide.with(this).load(sourceUri).listener(new RequestListener<Drawable>() {
#Override
public boolean onLoadFailed(#Nullable GlideException e, Object model, Target<Drawable> target, boolean isFirstResource) {
return false;
}
#Override
public boolean onResourceReady(Drawable resource, Object model, Target<Drawable> target, DataSource dataSource, boolean isFirstResource) {
// Get original bitmap
sourceBitmap = ((BitmapDrawable)resource).getBitmap();
// Create a new bitmap corresponding to the crop area
int[] cropAreaXY = new int[2];
int[] placeHolderXY = new int[2];
Rect rect = new Rect();
imageView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener(){
#Override
public boolean onPreDraw() {
try {
imageView.getLocationOnScreen(placeHolderXY);
cropArea.getLocationOnScreen(cropAreaXY);
cropArea.getGlobalVisibleRect(rect);
croppedBitmap = Bitmap.createBitmap(sourceBitmap, cropAreaXY[0], cropAreaXY[1] - placeHolderXY[1], rect.width(), rect.height());
// Save the croppedBitmap if you wish
getActivity().runOnUiThread(() -> imageView.setImageBitmap(croppedBitmap));
return true;
}finally {
imageView.getViewTreeObserver().removeOnPreDrawListener(this);
}
}
});
return false;
}
}).into(imageView);
}
fragment_camera.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#android:color/black">
<androidx.camera.view.PreviewView
android:id="#+id/preview"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintDimensionRatio="3:4"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<View
android:id="#+id/crop_area"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_margin="8dp"
android:background="#drawable/rectangle_round_corners"
app:layout_constraintBottom_toBottomOf="#+id/preview"
app:layout_constraintDimensionRatio="4.5:3"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<View
android:id="#+id/cameraBottomView"
android:layout_width="match_parent"
android:layout_height="0dp"
android:background="#android:color/black"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/preview" />
<ImageButton
android:id="#+id/btn_capture"
android:layout_width="64dp"
android:layout_height="64dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:background="#drawable/ic_shutter"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/preview" />
</androidx.constraintlayout.widget.ConstraintLayout>
fragment_gallery.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/layout_main"
android:background="#android:color/black"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="#+id/image_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="visible"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<View
android:id="#+id/crop_area"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_margin="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintDimensionRatio="4.5:3"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

If you want the image to be cropped to whichever your PreviewView is showing, just do:
val useCaseGroup = UseCaseGroup.Builder()
.addUseCase(preview!!)
.addUseCase(imageCapture!!)
.setViewPort(previewView.viewPort!!)
.build()
camera = cameraProvider.bindToLifecycle(
this, cameraSelector, useCaseGroup)

Related

Artefacts when implementing expandable items in RecyclerView

I'm trying to make a simple RecyclerView with expandable items using Kotlin. Basically a list of bins which expand out to show descriptions of what should go in them. I tried to follow the Android Studio Recycler View example as closely as possible. The problem is when I expand each item, some artefacts would show.
Unexpanded view
Expanded view
Note: The expanded view is the result of pressing on "Recycling Bin", the artefact is made up of the bin's description text and also "Garden Waste Bin" somehow being duplicated.
Below is my implementation
Adapter class
class BinAdapter(private val dataSet: List<Bin>) : RecyclerView.Adapter<BinViewHolder>() {
var expandedPosition = -1
var previousExpandedPosition = -1
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BinViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.bin_row_item, parent, false)
return BinViewHolder(view)
}
override fun onBindViewHolder(viewHolder: BinViewHolder, position: Int) {
viewHolder.itemView.isActivated
viewHolder.titleView.text = dataSet[position].name
viewHolder.descriptionView.text = dataSet[position].description
val isExpanded = position == expandedPosition
viewHolder.descriptionView.visibility = if (isExpanded) View.VISIBLE else View.GONE
viewHolder.itemView.isActivated = isExpanded
if (isExpanded) {
previousExpandedPosition = position
}
viewHolder.itemView.setOnClickListener {
expandedPosition = if (isExpanded) -1 else position
notifyItemChanged(previousExpandedPosition)
notifyItemChanged(position)
}
}
override fun getItemCount() = dataSet.size
Fragment class
class BinLookupFragment : Fragment() {
private var _binding: FragmentBinLookupBinding? = null
// This property is only valid between onCreateView and
// onDestroyView.
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentBinLookupBinding.inflate(inflater, container, false)
binding.binRecyclerView.adapter = BinAdapter(BinList(resources))
return binding.root
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
ViewHolder class
class BinViewHolder(itemView: View) : ViewHolder(itemView) {
val titleView: TextView
val descriptionView: TextView
init {
titleView = itemView.findViewById(R.id.titleView)
descriptionView = itemView.findViewById(R.id.descriptionView)
}
}
Row item layout
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="#+id/titleView"
android:layout_width="#dimen/bin_list_width"
android:layout_height="#dimen/bin_list_item_title_height"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
/>
<TextView
android:id="#+id/descriptionView"
android:layout_width="#dimen/bin_list_width"
android:layout_height="#dimen/bin_list_item_description_height"
app:layout_constraintTop_toBottomOf="#id/titleView"
app:layout_constraintStart_toStartOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
fragment layout
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/bin_recycler_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginTop="200dp"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"/>
</androidx.constraintlayout.widget.ConstraintLayout>
Has anyone come across this before or have some debugging tips?

Failed to take picture from arFragment - ARCore - Augmented Face

I follow code here but failed to capture my ArFragment. Please help me.
https://codelabs.developers.google.com/codelabs/sceneform-intro/index.html?index=..%2F..io2018#15
This project's link: https://github.com/DSparda/Breakable-Cam
Always get IOException: "Failed to save bitmap to disk"
My WritingArFragment:
class FaceArFragment : ArFragment() {
override fun getSessionConfiguration(session: Session?): Config {
return Config(session).apply { augmentedFaceMode = Config.AugmentedFaceMode.MESH3D }
}
override fun getSessionFeatures(): MutableSet<Session.Feature> {
return EnumSet.of(Session.Feature.FRONT_CAMERA)
}
override fun getAdditionalPermissions(): Array<String?>? {
val additionalPermissions = super.getAdditionalPermissions()
val permissionLength =
additionalPermissions?.size ?: 0
val permissions =
arrayOfNulls<String>(permissionLength + 1)
permissions[0] = Manifest.permission.WRITE_EXTERNAL_STORAGE
if (permissionLength > 0) {
System.arraycopy(
additionalPermissions,
0,
permissions,
1,
additionalPermissions!!.size
)
}
return permissions
}
/**
* Override to turn off planeDiscoveryController. Plane trackables are not supported with the
* front camera.
*/
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
val layout = super.onCreateView(inflater, container, savedInstanceState) as FrameLayout
planeDiscoveryController.apply {
hide()
setInstructionView(null)
}
return layout
}
}
My Activity XML:
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="takePictureViewModel"
type="com.example.breakablecam.screens.takingPicture.TakePictureViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.fragment.app.FragmentContainerView
android:id="#+id/face_fragment_cointanier"
android:layout_width="match_parent"
android:layout_height="0dp"
android:paddingBottom="16dp"
app:layout_constraintBottom_toTopOf="#+id/takePhotoView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:visibility="visible">
<fragment
android:id="#+id/face_fragment"
android:name="com.example.breakablecam.screens.takingPicture.FaceArFragment"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</androidx.fragment.app.FragmentContainerView>
<ImageView
android:id="#+id/backArrow"
android:layout_width="#android:dimen/app_icon_size"
android:layout_height="#android:dimen/app_icon_size"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<ImageView
android:id="#+id/stickerView"
android:layout_width="#android:dimen/app_icon_size"
android:layout_height="#android:dimen/app_icon_size"
android:layout_marginBottom="16dp"
android:onClick="#{() -> takePictureViewModel.tapSticker()}"
android:visibility="visible"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="#+id/takePhotoView"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent" />
<ImageView
android:id="#+id/stickerView1"
android:layout_width="#android:dimen/app_icon_size"
android:layout_height="#android:dimen/app_icon_size"
android:layout_marginStart="16dp"
android:layout_marginBottom="16dp"
android:visibility="gone"
android:onClick="#{() -> takePictureViewModel.tapSticker1()}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<ImageView
android:id="#+id/takePhotoView"
android:layout_width="#android:dimen/app_icon_size"
android:layout_height="#android:dimen/app_icon_size"
android:layout_marginBottom="16dp"
android:visibility="visible"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="#+id/makeupView"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="#+id/stickerView" />
<ImageView
android:id="#+id/makeupView"
android:layout_width="#android:dimen/app_icon_size"
android:layout_height="#android:dimen/app_icon_size"
android:layout_marginBottom="16dp"
android:onClick="#{() -> takePictureViewModel.tapMakeupView()}"
android:visibility="visible"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="#+id/takePhotoView" />
<ImageView
android:id="#+id/makeupView1a"
android:layout_width="#android:dimen/app_icon_size"
android:layout_height="#android:dimen/app_icon_size"
android:layout_marginStart="16dp"
android:layout_marginBottom="16dp"
android:onClick="#{() -> takePictureViewModel.tapMakeup1aView()}"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
tools:visibility="visible" />
<ImageView
android:id="#+id/makeupView1"
android:layout_width="#android:dimen/app_icon_size"
android:layout_height="#android:dimen/app_icon_size"
android:layout_marginStart="16dp"
android:visibility="gone"
app:layout_constraintBottom_toTopOf="#id/makeupView1a"
app:layout_constraintStart_toStartOf="parent"
tools:visibility="visible" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
My Activity:
class TakePictureActivity : AppCompatActivity() {
private lateinit var viewModel: TakePictureViewModel
private lateinit var arFragment: FaceArFragment
private var modelRenderable: ModelRenderable? = null
private var meshTexture: Texture? = null
private val faceNodeMap = HashMap<AugmentedFace, AugmentedFaceNode?>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding: ActivityTakePictureBinding =
DataBindingUtil.setContentView(this, R.layout.activity_take_picture)
viewModel = ViewModelProvider(this).get(TakePictureViewModel::class.java)
binding.apply {
viewModel.apply {
setStickerViewSource(stickerView)
setMakeupViewSource(makeupView)
setTakePhotoViewSource(takePhotoView)
setMakeup1ViewSource(makeupView1)
setMakeup1aViewSource(makeupView1a)
setSticker1ViewSource(stickerView1)
setBackArrowSource(backArrow)
}
}
binding.takePictureViewModel = viewModel
binding.lifecycleOwner = this
viewModel.makeupTap.observe(this, Observer { check ->
if (check == 1) {
binding.apply {
makeupView.visibility = GONE
takePhotoView.visibility = GONE
stickerView.visibility = GONE
makeupView1.visibility = VISIBLE
makeupView1a.visibility = VISIBLE
backArrow.visibility = VISIBLE
val params =
faceFragmentCointanier.layoutParams as ConstraintLayout.LayoutParams
params.bottomToTop = R.id.makeupView1
}
viewModel.doneTapMakeup()
}
})
viewModel.stickerTap.observe(this, Observer { check ->
if (check == 1) {
binding.apply {
makeupView.visibility = GONE
takePhotoView.visibility = GONE
stickerView.visibility = GONE
stickerView1.visibility = VISIBLE
backArrow.visibility = VISIBLE
val params =
faceFragmentCointanier.layoutParams as ConstraintLayout.LayoutParams
params.bottomToTop = R.id.stickerView1
}
viewModel.doneTapSticker()
}
})
binding.apply {
backArrow.setOnClickListener {
makeupView.visibility = VISIBLE
takePhotoView.visibility = VISIBLE
stickerView.visibility = VISIBLE
makeupView1.visibility = GONE
makeupView1a.visibility = GONE
backArrow.visibility = GONE
stickerView1.visibility = GONE
backArrow.visibility = GONE
val params =
faceFragmentCointanier.layoutParams as ConstraintLayout.LayoutParams
params.bottomToTop = R.id.takePhotoView
}
}
arFragment = supportFragmentManager.findFragmentById(R.id.face_fragment) as FaceArFragment
val sceneView = arFragment.arSceneView
sceneView.cameraStreamRenderPriority = Renderable.RENDER_PRIORITY_FIRST
val scene = sceneView.scene
loadTexture(R.drawable.makeup1a)
loadModel(R.raw.fox_face)
viewModel.makeupTap1a.observe(this, Observer { check ->
when (check) {
1 -> {
scene.addOnUpdateListener {
val collection: Collection<AugmentedFace>? =
sceneView.session?.getAllTrackables(AugmentedFace::class.java)
collection?.forEach { face ->
if (!faceNodeMap.containsKey(face)) {
val faceNode = AugmentedFaceNode(face)
faceNode.apply {
setParent(scene)
faceMeshTexture = meshTexture
}
faceNodeMap[face] = faceNode
}
}
val iterator = faceNodeMap.entries.iterator()
while (iterator.hasNext()) {
val entry = iterator.next()
val face = entry.key
if (face.trackingState == TrackingState.STOPPED) {
val faceNode = entry.value
faceNode!!.setParent(null)
iterator.remove()
}
}
}
}
2 -> {
val children: List<Node> =
ArrayList(arFragment.arSceneView.scene.children)
for (node in children) {
if (node is AnchorNode) {
if (node.anchor != null) {
node.anchor?.detach()
}
}
if (node !is Camera && node !is Sun) {
node.setParent(null)
}
}
}
}
})
viewModel.sticker1Tap.observe(this, Observer { check ->
when (check) {
1 -> {
scene.addOnUpdateListener {
val collection: Collection<AugmentedFace>? =
sceneView.session?.getAllTrackables(AugmentedFace::class.java)
collection?.forEach { face ->
if (!faceNodeMap.containsKey(face)) {
val faceNode = AugmentedFaceNode(face)
faceNode.apply {
setParent(scene)
faceRegionsRenderable = modelRenderable
}
faceNodeMap[face] = faceNode
}
}
val iterator = faceNodeMap.entries.iterator()
while (iterator.hasNext()) {
val entry = iterator.next()
val face = entry.key
if (face.trackingState == TrackingState.STOPPED) {
val faceNode = entry.value
faceNode!!.setParent(null)
iterator.remove()
}
}
}
}
2 -> {
val children: List<Node> =
ArrayList(arFragment.arSceneView.scene.children)
for (node in children) {
if (node is AnchorNode) {
if (node.anchor != null) {
node.anchor?.detach()
}
}
if (node !is Camera && node !is Sun) {
node.setParent(null)
}
}
}
}
})
binding.takePhotoView.setOnClickListener {
takePhoto()
}
}
private fun loadTexture(tex: Int) {
Texture.builder()
.setSource(this, tex)
.build()
.thenAccept { texture -> meshTexture = texture }
}
private fun loadModel(mod: Int) {
ModelRenderable.builder()
.setSource(this, mod)
.build()
.thenAccept { model ->
model.apply {
isShadowCaster = false // optional
isShadowReceiver = false
}
modelRenderable = model
}
}
private fun generateFilename(): String? {
val date =
SimpleDateFormat("yyyyMMddHHmmss", Locale.getDefault())
.format(Date())
return Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_PICTURES
).toString() + File.separator + "Sceneform/" + date + "_screenshot.jpg"
}
#Throws(IOException::class)
private fun saveBitmapToDisk(bitmap: Bitmap, filename: String) {
val out = File(filename)
if (!out.parentFile.exists()) {
out.parentFile.mkdirs()
}
try {
FileOutputStream(filename).use { outputStream ->
ByteArrayOutputStream().use { outputData ->
bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputData)
outputData.writeTo(outputStream)
outputStream.flush()
outputStream.close()
}
}
} catch (ex: IOException) {
throw IOException("Failed to save bitmap to disk", ex)
}
}
private fun takePhoto() {
val filename = generateFilename()
val view: ArSceneView = arFragment.getArSceneView()
// Create a bitmap the size of the scene view.
val bitmap = Bitmap.createBitmap(
view.width, view.height,
Bitmap.Config.ARGB_8888
)
// Create a handler thread to offload the processing of the image.
val handlerThread = HandlerThread("PixelCopier")
handlerThread.start()
// Make the request to copy.
PixelCopy.request(view, bitmap, { copyResult ->
if (copyResult === PixelCopy.SUCCESS) {
try {
saveBitmapToDisk(bitmap, filename!!)
} catch (e: IOException) {
val toast = Toast.makeText(
this, e.toString(),
Toast.LENGTH_LONG
)
toast.show()
return#request
}
val snackbar = Snackbar.make(
findViewById(android.R.id.content),
"Photo saved", Snackbar.LENGTH_LONG
)
snackbar.setAction(
"Open in Photos"
) { v: View? ->
val photoFile = File(filename)
val photoURI = FileProvider.getUriForFile(
this,
this.getPackageName()
.toString() + ".ar.codelab.name.provider",
photoFile
)
val intent = Intent(Intent.ACTION_VIEW, photoURI)
intent.setDataAndType(photoURI, "image/*")
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
startActivity(intent)
}
snackbar.show()
} else {
val toast = Toast.makeText(
this,
"Failed to copyPixels: $copyResult", Toast.LENGTH_LONG
)
toast.show()
}
handlerThread.quitSafely()
}, Handler(handlerThread.looper))
}
}
Thank everyone so much.
try add
<application android:requestLegacyExternalStorage="true" ... > ... </application>
to your manifest.
I assume that this is related to the upcoming scoped storage enforcement in Android 11.
You have to add the tags in your AndroidManifest.xml specified in the codelabs(permissions and file provider.
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="${applicationId}.ar.codelab.name.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="#xml/paths"/>
</provider>
Then you have to migrate from using File to using Uris and ContentResolvers because Android 10 deprecated the File functionality and you have to use MediaStore.
https://developer.android.com/training/data-storage/shared/media

How to write the adapter class for the following recycler view?

I am trying to make a Todo app and I have done the Room part and able to store data now I want to display the data in the form of Recycler View but I am not getting how to write the adapter class corresponding to it. I looked for it in different sites I never got any satisfying answer.
**TodoFragViewModel.kt""
class TodofragViewModel(
val database: TodoDao, applicaltion: Application
): AndroidViewModel(applicaltion) {
// TODO: Implement the ViewModel
/**
* viewModelJob allows us to cancel all coroutines started by this ViewModel.
*/
private var viewModelJob = Job()
/**All coroutines can be cancelled by viewmodelJob.cancel() and Dispatcher.main is byDefault choice
*/
private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob)
private val currenctTodo = MutableLiveData<Todo?>()
private val allTodo = database.getAllTodo()
init{
intializeThisTodo()
}
private fun intializeThisTodo(){
uiScope.launch {
currenctTodo.value=getFromDatabase()
}
}
private suspend fun getFromDatabase(): Todo? {
return withContext(Dispatchers.IO){
val info =database.getCurrentTodo()
info
}
}
private suspend fun insert(thisTodo: Todo) {
withContext(Dispatchers.IO) {
database.insert(thisTodo)
Log.i("Database","${database.getCurrentTodo()?.description} and ${database.getCurrentTodo()?.time}")
}
}
fun onAdded(time:String,description:String) {
uiScope.launch {
val thisTodo = Todo(time,description)
insert(thisTodo)
currenctTodo.value=getFromDatabase()
}
}
/**
* Called when the ViewModel is dismantled.
* At this point, we want to cancel all coroutines;
* otherwise we end up with processes that have nowhere to return to
* using memory and resources.
*/
override fun onCleared() {
super.onCleared()
viewModelJob.cancel()
}
}
todo_recycler_view
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_height="wrap_content">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="#+id/date_text"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:text="TextView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="#+id/todo_description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:text="TextView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/date_text" />
</androidx.constraintlayout.widget.ConstraintLayout>
TodoFrag.kt
class todofrag : Fragment() {
companion object {
fun newInstance() = todofrag()
}
private lateinit var viewModel: TodofragViewModel
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.todofrag_fragment, container, false)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
val application = requireNotNull(this.activity).application
val dataSource= TodoDatabase.getInstance(application)?.InformationDatabaseDao
val viewModelFactory = dataSource?.let { TodoViewModelFactory(it, application) }
val viewModel=ViewModelProviders.of(this,viewModelFactory).get(TodofragViewModel::class.java)
add_button.setOnClickListener{
val currentDate: String = SimpleDateFormat("dd/MM/yyyy", Locale.getDefault()).format(Date())
val currentTime: String = SimpleDateFormat("HH:mm:ss", Locale.getDefault()).format(Date())
val time:String="${currentDate} \n ${currentTime}"
viewModel.onAdded(time,todo_text.text.toString())
}
}
}
Please let me know if any other files are added. By the way, I tried to use card view so that it looked good.
The developer documentation explains it pretty well.
This might not be perfectly suited for what you need, but it should be a good start. Specifically, I don't know all the fields for your Todo class, so make sure you account for those in this code.
Basically, you'll want to have a ViewHolder that represents your CardView
class TodoViewHolder(convertView: View) : RecyclerView.ViewHolder(convertView) {
val dateText = convertView.findViewById(R.id.date_text)
val description = convertView.findViewById(R.id.todo_description)
// whatever else you need access to
}
And you'll want to use DiffUtil for a better user experience. This allows for some animations when things in the list change, such as removing an item, editing an item, or adding an item.
private class TodoDiffCallback : DiffUtil.ItemCallback<Todo>() {
override fun areItemsTheSame(oldItem: Todo, newItem: Todo) =
oldItem.id == newItem.id
override fun areContentsTheSame(oldItem: Todo, newItem: Todo) =
oldItem.dateText == newItem.dateText && oldItem.description == newItem.description
}
You'll want to extend ListAdapter and override its methods. onCreateViewHolder creates an instance of your TodoViewHolder for each view that is seen and onBindViewHolder allows you to add behavior to each item in the list. It is worth noting that you can pass parameter into the adapter in case you need to.
class MyListAdapter : ListAdapter<Todo, TodoViewHolder>(TodoDiffCallback()) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = TodoViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.todo_recycler_view, parent, false))
override fun onBindViewHolder(holder: TodoViewHolder, position: Int) {
val todo = getItem(position)
holder.dateText = todo.dateText
holder.description = todo.description
// add whatever click listener and other stuff you need
}
}
In your fragment, when you access your RecyclerView, just add an instance of the adapter if it's null.
if (recyclerView.adapter == null) {
recyclerView.adapter = TotoListAdapter()
}
And when you want to add data (that you have retrieved from Room or your API) to the adapter (in the fragment/activity), just do the following:
(recyclerView.adapter as? TodoListAdapter)?.submitList(data)
On a side note, make sure to clean up your style (you can use the Reformat Code command in the Code menu), and you would want to rename the todo_recycler_view to something like todo_view. You'll want to have a RecyclerView layout in your fragment layout.

Using recycleview in android

I read some document about using recyclingview for activity. Now i try to use recycleview to my fragment. the problem is my fragment look empty when i execute.
fragment:
class KategoriFragment : Fragment() {
var araclarKategori = ArrayList<AracMarka>()
private lateinit var galleryViewModel: GalleryViewModel
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
veriKaynaginiDoldur()
galleryViewModel =
ViewModelProviders.of(this).get(GalleryViewModel::class.java)
var root = inflater.inflate(R.layout.fragment_category, container, false)
veriKaynaginiDoldur()
var rvarackategori = root.findViewById(R.id.rvarackategori) as RecyclerView
var MyAdapter = AracMarkaAdapter(araclarKategori)
rvarackategori.adapter = MyAdapter
/
return root
}
fun veriKaynaginiDoldur(): ArrayList<AracMarka> {
var aracLogo = arrayOf(R.drawable.opellogo, R.drawable.chevroletlogo)
var aracismi = resources.getStringArray(R.array.arabaisim)
for (i in 0 until min(aracismi.size, aracLogo.size)) {
var eklenecekaracKategori = AracMarka(aracismi[i], aracLogo[i])
araclarKategori.add(eklenecekaracKategori)
}
return araclarKategori
}
}
I create an adapter. I think there is no problem on it.
adapter:
class AracMarkaAdapter(tumKategori: ArrayList<AracMarka>) :
RecyclerView.Adapter<AracMarkaAdapter.AracMarkaViewHolder>() {
var araclar = tumKategori
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AracMarkaViewHolder {
var inflater = LayoutInflater.from(parent.context)
var arackategori = inflater.inflate(R.layout.arac_kategori, parent, false)
return AracMarkaViewHolder(arackategori)
}
override fun getItemCount(): Int {
return araclar.size
}
override fun onBindViewHolder(holder: AracMarkaViewHolder, position: Int) {
holder.aracismi.text=araclar.get(position).aracAdi
holder.aracLogo.setImageResource(araclar.get(position).aracLogo)
}
class AracMarkaViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var teksatirKategori= itemView
var aracismi= teksatirKategori.tvaracAdi
var aracLogo=teksatirKategori.img_arac_sembol
}
}
fragment xml:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/rvarackategori"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
So, when i click button, fragment opens but it is empty. Do you have any idea about it?
After modifying the data in your list in veriKaynaginiDoldur(), you need to call myAdapter.notifyDataSetChanged() so it knows to rebind the data. Or you could call veriKaynaginiDoldur() before you instantiate your adapter.
Edit: Your other error is in your for loop within veriKaynaginiDoldur(). You are making a range using the size of the araclarKategori list when it is still zero.
Instead of
for (i in 0..araclarKategori.size - 1)
use
for (i in 0 until min(aracLogo.size, aracismi.size))
you have to call this veriKaynaginiDoldur() function after the below
lines of code below I have mentioned please check
var rvarackategori = root.findViewById(R.id.rvarackategori) as RecyclerView
var MyAdapter = AracMarkaAdapter(araclarKategori)
rvarackategori.adapter = MyAdapter
veriKaynaginiDoldur()

Android: Attempt to invoke virtual method on a null object reference in onBindViewHolder

My app is supposed to parse JSON data and display it in a recycler view. When I run the app, it crashes with the following error:
I've re-checked my code multiple times to make sure I am calling the correct resource files, but I still cant find the source of the issue. The logcat tells me the issue is in the onBindViewHolder method in the recycler view adapter class, but everything looks normal to me. Can anyone point me in the right direction? Here is the relevant code:
Recycler View Adapter:
public class IndRvAdapter extends RecyclerView.Adapter<IndRvAdapter.MyViewHolder> {
private Context context;
private List<Ind> indList;
public IndRvAdapter(Context context, List<Ind> indList) {
this.context = context;
this.indList = indList;
}
#NonNull
#Override
public MyViewHolder onCreateViewHolder(#NonNull ViewGroup viewGroup, int i) {
View view;
LayoutInflater inflater = LayoutInflater.from(context);
view = inflater.inflate(R.layout.ind_card, viewGroup, false);
final MyViewHolder viewHolder = new MyViewHolder(view);
viewHolder.indView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Intent i = new Intent(context, SingleInd.class);
i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
i.putExtra("pacshort", indList.get(viewHolder.getAdapterPosition()).getPacshort());
i.putExtra("supopp", indList.get(viewHolder.getAdapterPosition()).getSuppopp());
i.putExtra("candname", indList.get(viewHolder.getAdapterPosition()).getCandname());
i.putExtra("district", indList.get(viewHolder.getAdapterPosition()).getDistrict());
i.putExtra("amount", indList.get(viewHolder.getAdapterPosition()).getAmount());
i.putExtra("party", indList.get(viewHolder.getAdapterPosition()).getExpParty());
i.putExtra("payee", indList.get(viewHolder.getAdapterPosition()).getPayee());
i.putExtra("date", indList.get(viewHolder.getAdapterPosition()).getDate());
i.putExtra("origin", indList.get(viewHolder.getAdapterPosition()).getOrigin());
i.putExtra("source", indList.get(viewHolder.getAdapterPosition()).getSource());
context.startActivity(i);
}
});
return viewHolder;
}
#Override
public void onBindViewHolder(#NonNull MyViewHolder myViewHolder, int i) {
myViewHolder.pacshorts.setText(indList.get(i).getPacshort());
myViewHolder.supOpp.setText(indList.get(i).getSuppopp());
myViewHolder.candName.setText(indList.get(i).getCandname());
myViewHolder.district.setText(indList.get(i).getDistrict());
}
#Override
public int getItemCount() {
return indList.size();
}
public class MyViewHolder extends RecyclerView.ViewHolder{
TextView pacshorts, supOpp, candName, district;
LinearLayout indView;
public MyViewHolder(#NonNull View itemView) {
super(itemView);
pacshorts = itemView.findViewById(R.id.tv_pacshort);
supOpp = itemView.findViewById(R.id.tv_suppopp);
candName = itemView.findViewById(R.id.tv_candname);
district = itemView.findViewById(R.id.tv_district);
indView = itemView.findViewById(R.id.ind_view);
}
}
}
Card View resource:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:id="#+id/ind_view">
<android.support.v7.widget.CardView
android:layout_width="match_parent"
android:layout_height="250dp"
android:layout_gravity="center"
android:layout_margin="5dp"
android:elevation="3dp"
card_view:cardCornerRadius="15dp"
card_view:cardElevation="4dp"
card_view:cardUseCompatPadding="true">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/colorPrimaryDark">
<TextView
android:id="#+id/pacshort"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:text="TextView" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="#+id/pacshort"
android:id="#+id/tv_suppop"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/tv_candname"
android:layout_below="#+id/tv_suppop"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/tv_district"
android:layout_below="#+id/tv_candname"/>
</RelativeLayout>
</android.support.v7.widget.CardView>
</LinearLayout>
Recycler View:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/background_color"
tools:context=".IndExpend">
<android.support.v7.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/ind_rv">
</android.support.v7.widget.RecyclerView>
</RelativeLayout>
Main Activity that parses the JSON data
public class IndExpend extends AppCompatActivity {
/*
* Open secrets API: Returns the 50 latest independent expenditures transactions
* Updated every 4 days
*/
private static final String url = "http://www.opensecrets.org/api/?method=independentExpend&output=json&apikey=d1ff8f708ca0745d75e9ffa0ee6f3d09";
private LinearLayoutManager linearLayoutManager;
private List <Ind> indList;
private RecyclerView myrv;
private RecyclerView.Adapter adapter;
public IndExpend(){}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_ind_expend);
linearLayoutManager = new LinearLayoutManager(this);
linearLayoutManager.setOrientation(LinearLayoutManager.VERTICAL);
myrv = findViewById(R.id.ind_rv);
indList = new ArrayList<>();
adapter = new IndRvAdapter(this, indList);
myrv.setHasFixedSize(true);
myrv.setLayoutManager(linearLayoutManager);
myrv.setAdapter(adapter);
getData();
}
private void getData() {
final ProgressDialog progressDialog = new ProgressDialog(this);
progressDialog.setMessage("Loading...");
progressDialog.show();
StringRequest stringRequest = new StringRequest(Request.Method.GET, url, new Response.Listener<String>() {
#Override
public void onResponse(String response) {
progressDialog.dismiss();
try {
JSONObject object = new JSONObject (response);
JSONObject responseObj = object.getJSONObject("response");
JSONArray array = responseObj.getJSONArray("indexp");
for (int i = 0; i < array.length(); i++){
JSONObject attributesObj = array.getJSONObject(i).getJSONObject("#attributes");
Ind ind = new Ind(attributesObj.getString("pacshort"),
attributesObj.getString("suppopp"),
attributesObj.getString("candname"),
attributesObj.getString("district"),
attributesObj.getString("amount"),
attributesObj.getString("party"),
attributesObj.getString("payee"),
attributesObj.getString("date"),
attributesObj.getString("origin"),
attributesObj.getString("source"));
indList.add(ind);
}
adapter = new IndRvAdapter(getApplicationContext(), indList);
myrv.setAdapter(adapter);
}catch (JSONException e) {
e.printStackTrace();
}
}
}, new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
Log.e("Volley", error.toString());
progressDialog.dismiss();
}
});
RequestQueue requestQueue = Volley.newRequestQueue(this );
requestQueue.add(stringRequest);
}
}
Can anyone help me find the source of the problem?
I think it is because you are trying to bind the view with this id:
pacshorts = itemView.findViewById(R.id.tv_pacshort);
But on the layout file the id for that element its just pacshort:
<TextView
android:id="#+id/pacshort"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:text="TextView" />
Generally on these type of null pointer exceptions when working on Android happens for one of two reasons. First, the res file that's being inflated is not the right one or, second, the id used to bind the views is misspelled or doesn't exist in that view.