Spin Wheel App Source Code – Java, Jetpack Compose and Flutter

Looking to build a fun and engaging spin-the-wheel app for giveaways, lucky draws, games, or random selections? This post covers a complete mini wheel spinner app built with:

  • ✅ Android Java using Custom Views
  • ✅ Android Jetpack Compose
  • ✅ Cross-platform Flutter (Android, iOS, and Web)

It’s lightweight, beautifully animated, and perfect for learners and developers. Whether you’re creating a decision-maker app or adding gamification to your product — this is your go-to solution.




Download Source Code

🚀 Key Features

  • 🎯 Smooth spinning animation with auto-colored segments
  • 🧠 Callback on spin result (Java/Compose)
  • 📦 3 complete projects: Android Java, Jetpack Compose & Flutter
  • 🔁 Reusable spinner logic for games, quizzes, and lucky draw
  • 📱 Supports Android & Web (via Flutter)

📁 What’s Inside the ZIP

This repository includes a ZIP file with three fully working source code projects:

  • Android Java: Classic implementation using custom `View`
  • Jetpack Compose: Modern declarative UI with Compose
  • Flutter: Cross-platform app using Dart

⚙️ How to Use

  1. Download the ZIP using the button above
  2. Unzip and open the desired project in Android Studio or VS Code
  3. Click Run and enjoy your animated spinner!

🔗 Get more awesome source codes at: Source Codes


Tags: android java spin wheel, flutter lucky draw app, wheel spinner compose, source code android spinner, alsaeeddev, gamification app, random picker app

File Manager Pro – Android App Source Code

Looking for a production-ready Android file manager app source code written in Java? Look no further! File Manager Pro is a clean, fully functional, open-source file explorer built using modern development practices. It’s perfect for developers who want to learn, customize, or extend a real-world Android file manager project.


🚀 Key Features

  • ✅ Browse files and folders with a responsive UI
  • ✅ Toggle show/hide hidden files support
  • ✅ Multi-select with bottom menu actions: Copy, Move, Delete
  • ✅ List View (Recycler View)
  • ✅ File type icons and image previews
  • ✅ Works on Android 7+

🎯 Built Using

  • ✔️ Java (Android Studio)
  • ✔️ MVVM Architecture
  • ✔️ RecyclerView, ViewModel, LiveData
  • ✔️ XML-based custom layouts


Download Source Code

🌐 About the Developer

Al Saeed – Android App developer and blogger at Al Saeed. Passionate about open-source and clean architecture development.

🔗 Explore more free Android app source codes: View Source Codes

🔗 Learn Jetpack Compose UI development at: Official Android Documentation


🔖 Tags:

Android File Manager App, File Explorer Android Java, Open Source Android App, Java Android Source Code, Android File Browser GitHub, File Manager MVVM, Android Studio Project Free Download


Stunning Custom Analog Clock in Android – Java & Kotlin

Want to design a custom analog clock in Android? In this post, you’ll learn how to create one using both Java and Kotlin. Whether you’re building a utility app, dashboard UI, or simply improving your UI skills, this is the perfect project to implement a working analog clock in Android.

🎯 What’s Inside This Project?

You’ll get the complete source code for both languages, canvas-based drawing, second-by-second ticking hands, and a beautiful rounded design. Let’s dive into development!

✅ What You’ll Learn

  • How to draw a custom analog clock using Canvas
  • Java and Kotlin implementations side by side
  • Center dot, circular frame, and dynamic ticking hands
  • Modern, responsive UI layout for all screen sizes

This article provides two versions: AnalogClockView.java and AnalogClockViewKotlin.kt. You can use either based on your language preference.

📄 XML Layout

To use the custom clock in your activity, add this to your activity_main.xml:

<?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"
    tools:context=".MainActivity">

    <www.alsaeeddev.clock.AnalogClockView
        android:id="@+id/analogClock"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_centerInParent="true" />

</RelativeLayout>

📦 Java Code: AnalogClockView.java

 
package www.alsaeeddev.clock;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.View;

import java.util.Calendar;

public class AnalogClockView extends View {

    private Paint paint;
    private int width, height, radius;
    private int padding = 0;
    private int numeralSpacing = 30;
    private int handTruncation, hourHandTruncation;
    private final int[] numbers = {1,2,3,4,5,6,7,8,9,10,11,12};
    private Rect textBounds = new Rect();

    public AnalogClockView(Context context) {
        super(context);
        init();
    }

    public AnalogClockView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    private void init() {
        paint = new Paint();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        width = getWidth();
        height = getHeight();
        int min = Math.min(width, height);
        radius = min / 2 - 40;

        canvas.drawColor(Color.parseColor("#928dab")); // Background

        // Draw clock background
        paint.reset();
        paint.setColor(Color.parseColor("#b3f6f8"));
        paint.setStyle(Paint.Style.FILL);
        canvas.drawCircle((float) width /2, (float) height /2, radius + 30, paint);

        paint.setStyle(Paint.Style.STROKE);
        paint.setColor(Color.parseColor("#9e7419"));
        paint.setStrokeWidth(8f);
        canvas.drawCircle((float) width /2, (float) height /2, radius + 30, paint);

        // Draw center dot
        paint.setStyle(Paint.Style.FILL);
        paint.setColor(Color.BLACK);
        canvas.drawCircle((float) width /2, (float) height /2, 12, paint);

        // Draw clock numbers
        paint.setTextSize(40);
        paint.setColor(Color.BLACK);
        for (int number : numbers) {
            String tmp = String.valueOf(number);
            paint.getTextBounds(tmp, 0, tmp.length(), textBounds);
            double angle = Math.PI / 6 * (number - 3);
            int x = (int)((double) width /2 + Math.cos(angle) * (radius - numeralSpacing) - (double) textBounds.width() /2);
            int y = (int)((double) height /2 + Math.sin(angle) * (radius - numeralSpacing) + (double) textBounds.height() /2);
            canvas.drawText(tmp, x, y, paint);
        }

        // Get time
        Calendar calendar = Calendar.getInstance();
        int hour = calendar.get(Calendar.HOUR);
        int minute = calendar.get(Calendar.MINUTE);
        int second = calendar.get(Calendar.SECOND);

        drawHand(canvas, (hour + minute / 60.0) * 5f, true, false); // Hour hand
        drawHand(canvas, minute, false, false);                     // Minute hand
        drawHand(canvas, second, false, true);                      // Second hand

        postInvalidateDelayed(1000);
        invalidate();
    }



// draw the clock hands
    private void drawHand(Canvas canvas, double loc, boolean isHour, boolean isSecond) {
        double angle = Math.PI * loc / 30 - Math.PI / 2;
        int handRadius = isHour ? radius - 120 : radius - 60;
        if (isSecond) handRadius = radius - 40;

        paint.setColor(isSecond ? Color.parseColor("#e74c3c") : Color.BLACK);
        paint.setStrokeWidth(isSecond ? 4f : isHour ? 8f : 6f);
        canvas.drawLine((float) width /2, (float) height /2,
                (float)((double) width /2 + Math.cos(angle) * handRadius),
                (float)((double) height /2 + Math.sin(angle) * handRadius),
                paint);
    }
}

🧑‍💻 Kotlin Code: AnalogClockViewKotlin.kt

 
package www.alsaeeddev.clock

import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.Rect
import android.util.AttributeSet
import android.view.View
import java.util.Calendar
import kotlin.math.cos
import kotlin.math.min
import kotlin.math.sin

class AnalogClockViewKotlin @JvmOverloads constructor(
    context: Context, attrs: AttributeSet? = null
) : View(context, attrs) {

    private val paint = Paint()
    private val numbers = (1..12).toList()
    private val textBounds = Rect()
    private var widthCenter = 0
    private var heightCenter = 0
    private var radius = 0

    init {
        paint.isAntiAlias = true
    }

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)

        widthCenter = width / 2
        heightCenter = height / 2
        radius = min(widthCenter, heightCenter) - 60

        drawBackground(canvas)
        drawCenterDot(canvas)
        drawNumbers(canvas)
        drawHands(canvas)

        postInvalidateDelayed(1000)
        invalidate()
    }

    private fun drawBackground(canvas: Canvas) {
        paint.reset()
        paint.isAntiAlias = true

        // Clock circle fill
        paint.color = Color.parseColor("#b3f6f8")
        paint.style = Paint.Style.FILL
        canvas.drawCircle(widthCenter.toFloat(), heightCenter.toFloat(), radius + 30f, paint)

        // Clock border
        paint.style = Paint.Style.STROKE
        paint.strokeWidth = 8f
        paint.color = Color.parseColor("#9e7419")
        canvas.drawCircle(widthCenter.toFloat(), heightCenter.toFloat(), radius + 30f, paint)
    }

    private fun drawCenterDot(canvas: Canvas) {
        paint.style = Paint.Style.FILL
        paint.color = Color.BLACK
        canvas.drawCircle(widthCenter.toFloat(), heightCenter.toFloat(), 12f, paint)
    }

    private fun drawNumbers(canvas: Canvas) {
        paint.textSize = 40f
        paint.color = Color.BLACK
        for (number in numbers) {
            val numStr = number.toString()
            paint.getTextBounds(numStr, 0, numStr.length, textBounds)
            val angle = Math.PI / 6 * (number - 3)
            val x = (widthCenter + cos(angle) * (radius - 30) - textBounds.width() / 2).toFloat()
            val y = (heightCenter + sin(angle) * (radius - 30) + textBounds.height() / 2).toFloat()
            canvas.drawText(numStr, x, y, paint)
        }
    }

    private fun drawHands(canvas: Canvas) {
        val calendar = Calendar.getInstance()
        val hour = calendar.get(Calendar.HOUR)
        val minute = calendar.get(Calendar.MINUTE)
        val second = calendar.get(Calendar.SECOND)

        drawHand(canvas, (hour + minute / 60f) * 5, isHour = true, isSecond = false)
        drawHand(canvas, minute.toFloat(), isHour = false, isSecond = false)
        drawHand(canvas, second.toFloat(), isHour = false, isSecond = true)
    }

    private fun drawHand(canvas: Canvas, loc: Float, isHour: Boolean, isSecond: Boolean) {
        val angle = Math.PI * loc / 30 - Math.PI / 2
        val handLength = when {
            isHour -> radius - 120
            isSecond -> radius - 30
            else -> radius - 60
        }

        paint.color = if (isSecond) Color.parseColor("#e74c3c") else Color.BLACK
        paint.strokeWidth = when {
            isHour -> 8f
            isSecond -> 4f
            else -> 6f
        }
        paint.style = Paint.Style.STROKE

        canvas.drawLine(
            widthCenter.toFloat(), heightCenter.toFloat(),
            (widthCenter + cos(angle) * handLength).toFloat(),
            (heightCenter + sin(angle) * handLength).toFloat(),
            paint
        )
    }
}



✅ Conclusion

Creating a custom analog clock in Android is a great way to explore Canvas drawing, custom views, and elegant UI development. Whether you choose Java or Kotlin, the outcome is a clean and dynamic clock component ready for your next project.

We used the Paint class extensively to draw shapes and text on the canvas. To learn more about its capabilities, see the official documentation here:
🔗 Android Developers

Don’t forget to share and follow Al Saeed for more hands-on Android tutorials.

Generate AI Videos with InVideo AI

Are you tired of spending hours editing videos or struggling with complicated software? Whether you’re a content creator, marketer, or business owner, video is one of the most powerful tools for engaging your audience. But not everyone has the time or skills to create professional-quality videos — that’s where InVideo AI comes in.

That’s where InVideo AI comes in.


🚀 What Is InVideo AI?

InVideo AI is an advanced, AI-powered video creation tool that helps you turn your ideas into stunning videos—without needing any prior editing experience. With just a few prompts or a simple script, InVideo automatically generates a complete video, complete with visuals, music, transitions, and voiceovers.

Whether you’re making YouTube videos, Instagram reels, business promos, or ad creatives, InVideo AI handles the hard work for you.


✨ Key Features of InVideo AI

🤖 1. AI Script to Video

Just type your idea or paste a script—InVideo will turn it into a ready-to-publish video using AI-selected visuals and music.

🎤 2. AI Voiceovers

No need to record your own voice. Choose from dozens of realistic AI voice options in multiple languages.

📚 3. Templates for Every Industry

Choose from 5000+ professionally designed templates across niches like real estate, eCommerce, education, fitness, and more.

📱 4. Multi-Platform Export

Export in sizes perfect for YouTube, Instagram, Facebook, or TikTok in just a few clicks.

✏️ 5. Easy Customization

You can customize fonts, colors, branding, transitions, music, and more—even after the AI generates your video.


🎯 Who Should Use InVideo AI?

InVideo is perfect for:

  • YouTubers & Vloggers who want to create faster

  • Digital Marketers creating video ads or social posts

  • Small Business Owners promoting products

  • Freelancers & Agencies offering video content services

  • Teachers & Educators making engaging learning content


💡 Why Choose InVideo AI?

Unlike traditional video editors, InVideo focuses on speed, automation, and ease-of-use. You don’t need to spend hours editing or pay for expensive software like Adobe Premiere or Final Cut Pro.

InVideo gives you:

  • A massive library of stock videos & photos

  • Automatic subtitles & transitions

  • Free and premium plans to suit any budget


🔗 Try InVideo AI Now – Free!

You can get started with InVideo for free, and upgrade anytime if you need premium features.

👉 Click here to try InVideo AI and create your first video now


🏁 Final Thoughts

If you’re looking for an AI video editor that saves time, improves quality, and boosts engagement—InVideo is your best bet. Whether you’re a beginner or a pro, it’s a game-changer in how videos are made today.


Ready to bring your ideas to life?
👉 Start using InVideo AI now and see the magic for yourself.

📝 Want more AI-powered tools and content creation tips?
Check out our latest blog posts here and discover more ways to boost your productivity and creativity!

Free React Portfolio Website Template Using AI

Are you a developer, designer, freelancer, or student looking to create a standout personal portfolio website without starting from scratch? This Free React Portfolio Website Template, generated using the latest AI technologies, helps you launch your professional online presence effortlessly.

Whether you’re showcasing your skills, side projects, or freelance services, this template gives you a modern, responsive, and SEO-friendly starting point — built with React + Vite for lightning-fast performance.


🎯 Why Use This Free React Portfolio Website Template?

Here are the top reasons why this React portfolio template stands out:

Modern & Clean UI Design – Built with a sleek layout to attract and engage visitors
Powered by React & Vite – Ensures optimal build speed and performance
Responsive Design – Looks stunning on mobile, tablet, and desktop
Highly Customizable – Edit styles, sections, and content with ease
SEO Optimized – Fast load times, semantic HTML, and clean codebase
Open-Source & Free – Use it freely for your personal or professional portfolio

This Free React Portfolio Website Template is your shortcut to building a beautiful and professional portfolio in no time.


🔧 Technologies Used

  • React.js – Component-based frontend library

  • Vite – Next-generation frontend tooling

  • CSS Modules / Tailwind CSS (optional depending on customization)


Free Source Code

Click the button below to download the complete source code:

👉 Download the Source Code (ZIP)

Or explore the GitHub repository here:
🔗 View on GitHub

This Free React Portfolio Website Template is designed to save time while delivering a professional-grade user experience.


🖼️ Preview


🔗 Click here to Live Demo

💡 Use Cases for This Template

  • Developer or designer portfolio

  • Freelancer landing page

  • Student project showcase

  • Personal website or resume

  • Frontend project for job applications


⚙️ How to Get Started

  1. Download or clone the repo from GitHub

  2. Run npm install to install dependencies

  3. Run npm run dev to start local development

  4. Customize sections like Home, About, Projects, Skills, Contact

  5. Use npm run build to generate your production build

  6. Deploy to GitHub Pages, Netlify, or Vercel


📈 SEO Benefits of This Template

Using this Free React Portfolio Website Template ensures your site follows technical SEO best practices:

  • ⚡ Fast page load speed

  • 📱 Mobile-first responsive design

  • 🔗 Clean URL structure and metadata support

  • ✅ Google-friendly performance

This increases your chances of being discovered by recruiters, clients, and collaborators online.


🧠 Learn More

Want more tools and templates like this?
📚 Explore More on Our Blog

Media Player in Android Studio Using Kotlin | Media Player Source Code

If you’re looking to create a simple media player in Android Studio using Kotlin, this article will guide you through the core of your project: the MainActivity. This is where permissions are handled, songs are loaded from device storage, and user interactions are managed.

Overview of Media Player MainActivity

The MainActivity is responsible for:

  • Checking and requesting runtime permissions
  • Fetching music files from the device storage
  • Binding to the MusicService for background playback
  • Handling user interactions like play, pause, next, and previous

Kotlin Code Breakdown for Media Player

The class implements AppCompatActivity and ServiceConnection to establish communication with the background music service. It uses ActivityMainBinding for UI binding and coroutines to prevent blocking the main thread while querying songs from MediaStore.

Permission Handling for Media Player

For Android 13+ (Tiramisu), it requests READ_MEDIA_AUDIO. For earlier versions, READ_EXTERNAL_STORAGE is used.

Loading Songs

The app queries MediaStore.Audio.Media using a coroutine and populates the songsList if the files exist.

Binding Media Player to MusicService

On successful service connection, the list is passed to the service and the UI is updated with the currently playing song title.

Media Player Controls and Playback

The play/pause, next, and previous buttons update the state using MusicService. The UI is automatically refreshed using updateControls().

Other Media Player App Classes

To complete the media player, create the following Kotlin classes and paste their code accordingly:

  • MusicService.kt – Manages playback in the background
  • AudioModel.kt – Data class representing a song
  • MusicAdapter.kt – Binds song data to RecyclerView
  • MusicPlayerActivity.kt – Full screen music controls

1. MainActivity.kt


  class MainActivity : AppCompatActivity(), ServiceConnection {
    private var musicService: MusicService? = null
    private var isServiceBound = false
    private lateinit var binding: ActivityMainBinding
    private val songsList: MutableList = mutableListOf()
    private lateinit var adapter: MusicAdapter
    private var isPlaying = false

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)
        window.statusBarColor = Color.WHITE

        if (!checkPermission()) {
            requestPermission()

        } else {
            initializeApp()
        }

    }


    private fun initializeApp() {
        // Launch coroutine on Main thread
        CoroutineScope(Dispatchers.Main).launch {

            // Show progress bar
            binding.progressBar.visibility = View.VISIBLE

            val songs = withContext(Dispatchers.IO) {
                val songs = ArrayList()
                val projection = arrayOf(
                    MediaStore.Audio.Media.TITLE,
                    MediaStore.Audio.Media.DATA,
                    MediaStore.Audio.Media.DURATION
                )
                val selection = MediaStore.Audio.Media.IS_MUSIC + " !=0"

                contentResolver.query(
                    MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
                    projection,
                    selection,
                    null,
                    null
                )?.use { cursor ->
                    while (cursor.moveToNext()) {
                        val audioModel = AudioModel(
                            cursor.getString(1),
                            cursor.getString(0),
                            cursor.getString(2)
                        )
                        if (File(audioModel.path).exists()) {
                            songs.add(audioModel)
                        }
                    }
                }
                songs
            }

            // Hide progress bar
            binding.progressBar.visibility = View.GONE

            songsList.clear()
            songsList.addAll(songs)

            adapter = MusicAdapter(this@MainActivity, songsList) { position ->
                if (isPlaying) {
                    musicService?.musicPlay(songsList)
                    binding.songTitle.text = songsList[MyMediaPlayer.currentIndex].title
                    adapter.notifyDataSetChanged()
                } else {

                    if (songsList.isNotEmpty()) {
                        val intent = Intent(this@MainActivity, MusicPlayerActivity::class.java)
                        intent.putExtra("List", songsList as Serializable)
                        intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
                        startActivity(intent)
                    } else {
                        Toast.makeText(this@MainActivity, "No songs to play", Toast.LENGTH_SHORT)
                            .show()
                    }

                }
            }

            binding.recyclerView.layoutManager = LinearLayoutManager(this@MainActivity)
            binding.recyclerView.adapter = adapter
            binding.noSongFound.visibility = if (songsList.isEmpty()) View.VISIBLE else View.GONE

            setupControls()
        }
    }


    private fun setupControls() {
        val intent = Intent(this, MusicService::class.java)
        bindService(intent, this, Context.BIND_AUTO_CREATE)

        binding.pausePlay.setOnClickListener {
            musicService?.getMusicPlayer()?.let {
                if (it.isPlaying) {
                    it.pause()
                    binding.pausePlay.setImageResource(R.drawable.play_ic)
                } else {
                    it.start()
                    binding.pausePlay.setImageResource(R.drawable.pause_ic)
                }
            }
        }



        binding.next.setOnClickListener { musicNext() }
        binding.previous.setOnClickListener { musicPrevious() }

        binding.controls.setOnClickListener {
            val intent1 = Intent(this@MainActivity, MusicPlayerActivity::class.java)
            intent1.putExtra("IS", true)
            intent1.putExtra("List", songsList as Serializable)
            startActivity(intent1)
        }
    }


    private fun musicNext() {

        musicService?.next()
        binding.songTitle.text = songsList[MyMediaPlayer.currentIndex].title
        adapter.notifyDataSetChanged()

    }


    private fun musicPrevious() {
        musicService?.previous()
        binding.songTitle.text = songsList[MyMediaPlayer.currentIndex].title
        adapter.notifyDataSetChanged()

    }


    private fun checkPermission(): Boolean {

        val result = ContextCompat.checkSelfPermission(
            this@MainActivity,
            Manifest.permission.READ_EXTERNAL_STORAGE
        )

        if (result == PackageManager.PERMISSION_GRANTED) {
            return true

        } else {
            return false
        }
    }


    private fun requestPermission() {
        val permission = Manifest.permission.READ_EXTERNAL_STORAGE

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
            // For Android 13+ (Tiramisu), use READ_MEDIA_AUDIO instead
            ActivityCompat.requestPermissions(
                this,
                arrayOf(Manifest.permission.READ_MEDIA_AUDIO),
                92
            )
        } else {

            ActivityCompat.requestPermissions(
                this,
                arrayOf(permission),
                92
            )

        }
    }


    override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array,
        grantResults: IntArray
    ) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)

        if (requestCode == 92 && grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            Toast.makeText(this, "Permission granted", Toast.LENGTH_SHORT).show()
            initializeApp() // Or load your music files
        } else {
            Toast.makeText(this, "Permission denied", Toast.LENGTH_SHORT).show()
            requestPermission()
        }
    }


    override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
        val binder = service as MusicService.MyBinder
        musicService = binder.currentService()
        isServiceBound = true
        musicService!!.setSongLIst(songsList)

        // Update the controls based on the current state of the media player
        updateControls()
    }

    override fun onServiceDisconnected(name: ComponentName?) {
        musicService = null
        isServiceBound = false
    }


    private fun updateControls() {
        musicService?.getMusicPlayer()?.let { mediaPlayer ->
            if (mediaPlayer.isPlaying) {
                binding.controls.visibility = View.VISIBLE
                binding.songTitle.text = songsList[MyMediaPlayer.currentIndex].title
                binding.pausePlay.setImageResource(R.drawable.pause_ic)
                isPlaying = true
            } else {
                isPlaying = false
                binding.controls.visibility = View.GONE
            }

            mediaPlayer.setOnCompletionListener {
                musicNext() // Move to the next song when the current song is completed.
            }
        }

    }

    @SuppressLint("NotifyDataSetChanged")
    override fun onResume() {
        super.onResume()
        if (::adapter.isInitialized) {
            adapter.notifyDataSetChanged()
            updateControls()
        }

    }

    override fun onDestroy() {
        super.onDestroy()
        if (isServiceBound) {
            unbindService(this)
            isServiceBound = false
        }
    }
}

2. MusicService.kt


class MusicService : Service() {

    private var myBinder = MyBinder()
    private var mediaPlayer: MediaPlayer? = MyMediaPlayer.getInstance()
    private var currentSongPath: String? = null
    private var isAlreadyPlaying = false
    private var songsList: List? = null


    fun setSongLIst(list: List) {
        songsList = list

    }

    public fun getMusicPlayer(): MediaPlayer? {
        return mediaPlayer
    }


    override fun onBind(intent: Intent?): IBinder? {
        return myBinder

    }


    fun musicPlay(songList: List) {
        currentSongPath = songList[MyMediaPlayer.currentIndex].path
        mediaPlayer!!.reset()
        mediaPlayer!!.setDataSource(currentSongPath)
        mediaPlayer!!.prepare()
        mediaPlayer!!.start()

    }


    fun pause() {
        mediaPlayer?.pause()
        Log.d("MyService", "Pausing music")
    }

    fun stop() {
        mediaPlayer?.stop()
        Log.d("MyService", "Stopping music")
    }


    fun next() {
        if (MyMediaPlayer.currentIndex == songsList!!.size - 1) {
            return
        }

        MyMediaPlayer.currentIndex += 1
        mediaPlayer!!.reset()
        if (songsList != null) {
            musicPlay(songsList!!)
        }
        isAlreadyPlaying = false


    }


    fun previous() {
        if (MyMediaPlayer.currentIndex == 0) {
            return
        }
        MyMediaPlayer.currentIndex -= 1
        mediaPlayer!!.reset()
        if (songsList != null) {
            musicPlay(songsList!!)
        }
        isAlreadyPlaying = false

    }


    fun getPlayingStatus(): Boolean {
        return isAlreadyPlaying
    }


    fun isPlaying(): Boolean {
        return mediaPlayer?.isPlaying ?: false
    }

    fun getCurrentPosition(): Int {
        return mediaPlayer?.currentPosition ?: 0
    }

    fun seekTo(position: Int) {
        mediaPlayer?.seekTo(position)
    }


    override fun onDestroy() {
        super.onDestroy()
    }


    inner class MyBinder : Binder() {
        fun currentService(): MusicService {
            return this@MusicService
        }
    }

}

3. MusicAdapter.kt


class MusicAdapter(
    private val context: Context,
    private val songList: List,
    private val onClick: (Int) -> Unit
) : RecyclerView.Adapter() {


    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemHolder {
        val view = SongItemBinding.inflate(LayoutInflater.from(context), parent, false)
        return ItemHolder(view)
    }

    override fun getItemCount(): Int {
        return songList.size
    }


    public fun getSongLIst(): List {
        return songList
    }

    override fun onBindViewHolder(holder: ItemHolder, position: Int) {
        val audioSong = songList[position]
        holder.bind(audioSong, position)
    }


    inner class ItemHolder(private val binding: SongItemBinding) :
        RecyclerView.ViewHolder(binding.root) {


        fun bind(audioSong: AudioModel, position: Int) {

            if (MyMediaPlayer.currentIndex == position) {
                binding.tvSongTitle.setTextColor(Color.RED)
            } else {
                binding.tvSongTitle.setTextColor(Color.BLACK)
            }
            binding.tvSongTitle.text = audioSong.title
            //   binding.ivMusic.setImageResource(R.drawable.music_ic)

            binding.root.setOnClickListener {
                MyMediaPlayer.getInstance().reset()
                MyMediaPlayer.currentIndex = adapterPosition
                onClick(adapterPosition)


            }

        }

    }
}


Download Source Code

Conclusion

This Android media player in Kotlin is lightweight, efficient, and follows best practices for modern development using ViewBinding, coroutines, and service components. Add more features like notifications, shuffle, or repeat to further enhance your app.

You can also Check Out the official Android Media Player Documentation.
Want to learn more? Visit our tutorials at Blog .

Swipe and Tap Card Animation in Jetpack Compose

Creating an interactive card-based interface in Jetpack Compose is easier than ever. Today, we will explore how to implement a smooth Card Animation in Jetpack using swipe or tap gestures on cards with the CardSwipeOrTap composable. This approach is perfect for modern UI designs where users interact with content via gestures.

Main Activity Setup

We start by setting up the MainActivity that uses a Scaffold layout inside the RecyclerViewTheme. This ensures that our UI fills the entire screen while providing padding to our card interactions.

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            RecyclerViewTheme {
                Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
                    CardSwipeOrTap(modifier = Modifier.padding(innerPadding))
                }
            }
        }
    }
}

Building the Card Swipe or Tap Composable

The CardSwipeOrTap composable manages the card stack. It uses a Box to overlay cards on top of each other and applies a slight rotation and vertical offset for a realistic stacked effect.

Each card is individually rendered using the CardItem composable. We use a simple CoroutineScope to delay actions and allow for smooth animations between swipe or tap events.

@Composable
fun CardSwipeOrTap(modifier: Modifier = Modifier) {
    val scope = rememberCoroutineScope()
    var cards by remember { mutableStateOf(listOf(0, 1, 2)) }
    var isAnimating by remember { mutableStateOf(false) }

    val cardColors = listOf(
        Color(0xFFFF9800), // Orange
        Color(0xFFF44336), // Red
        Color(0xFF00E676)  // Light Green
    )

    Box(
        modifier = modifier.fillMaxSize().padding(32.dp),
        contentAlignment = Alignment.Center
    ) {
        cards.reversed().forEachIndexed { index, cardIndex ->
            CardItem(
                index = cardIndex,
                color = cardColors[cardIndex % cardColors.size],
                rotation = if (index == 0) 0f else if (index == 1) -10f else 10f,
                offsetY = (index * 20).dp,
                isTop = index == cards.size - 1,
                isAnimating = isAnimating,
                onSwiped = {
                    if (!isAnimating) {
                        isAnimating = true
                        scope.launch {
                            kotlinx.coroutines.delay(300)
                            val updated = cards.toMutableList()
                            val removed = updated.removeAt(0)
                            updated.add(removed)
                            cards = updated
                            isAnimating = false
                        }
                    }
                }
            )
        }
    }
}

Animating Card Movement with CardItem

The CardItem composable handles the animation when a user swipes or taps a card. It uses animateDpAsState and animateFloatAsState to animate the card’s offset and opacity, giving the appearance of a disappearing card when an interaction occurs.

@Composable
fun CardItem(
    index: Int,
    color: Color,
    rotation: Float,
    offsetY: Dp,
    isTop: Boolean,
    isAnimating: Boolean,
    onSwiped: () -> Unit
) {
    val animatedOffsetX by animateDpAsState(
        targetValue = if (isAnimating && isTop) 500.dp else 0.dp,
        animationSpec = if (isAnimating && isTop) tween(durationMillis = 500, easing = FastOutLinearInEasing) else snap(),
        label = "OffsetXAnimation"
    )

    val animatedAlpha by animateFloatAsState(
        targetValue = if (isAnimating && isTop) 0f else 1f,
        animationSpec = if (isAnimating && isTop) tween(durationMillis = 3000) else snap(),
        label = "AlphaAnimation"
    )

    Card(
        modifier = Modifier
            .fillMaxWidth(0.7f)
            .aspectRatio(1f)
            .offset(x = animatedOffsetX, y = offsetY)
            .graphicsLayer {
                rotationZ = rotation
                alpha = animatedAlpha
            }
            .pointerInput(isTop) {
                if (isTop && !isAnimating) {
                    detectDragGestures(
                        onDragEnd = { onSwiped() },
                        onDrag = { change, _ -> change.consume() }
                    )
                }
            }
            .pointerInput(isTop) {
                if (isTop && !isAnimating) {
                    detectTapGestures(
                        onTap = { onSwiped() }
                    )
                }
            },
        elevation = CardDefaults.cardElevation(defaultElevation = 8.dp),
        shape = RoundedCornerShape(20.dp),
        colors = CardDefaults.cardColors(
            containerColor = color,
            contentColor = Color.White
        )
    ) {
        Box(
            modifier = Modifier.fillMaxSize(),
            contentAlignment = Alignment.Center
        ) {
            Text(
                text = "Card #$index",
                style = MaterialTheme.typography.titleLarge,
                color = Color.White
            )
        }
    }
}


Final Thoughts for Card Animation in Jetpack

With just a few lines of Kotlin code, you can create a stunning card swipe and tap animation in Jetpack Compose. This approach is not only visually appealing but also highly interactive, offering a smooth user experience without the complexity of a traditional RecyclerView setup. Try it out in your next Android app project!

You can also read the official Jetpack Compose animation documentation.

Extract The Text From Image Using Python

Discover how to easily Extract the text From image using python to pdf and word file. This guide shows you how to build a Python Tkinter application that extracts text from images and saves it into Word or PDF files effortlessly.

Introduction

Manually extracting text from images can be a tedious task. Thankfully, Python offers powerful libraries like pytesseract and Tkinter to automate this process. In this tutorial, you’ll learn how to Extract the text From an image to pdf and word file by creating an easy-to-use desktop application. This project is perfect for beginners exploring GUI development and OCR (Optical Character Recognition).

 

Technologies Used

  • Python – Programming language
  • Tkinter – GUI toolkit for Python
  • pytesseract – Optical Character Recognition (OCR) tool
  • Pillow – Image processing library
  • fpdf – PDF creation library
  • python-docx – Word document generation library

 

Installation Guide for Technologies Used

Before building the project to Extract the Text from an Image Using Python, make sure to install the following required libraries and tools.

1. Install Python

Make sure you have Python installed. You can download it from the official site: Download Python.

2. Install Required Python Libraries
# Install tkinter (already included with Python on most systems)
# For Linux, you can install manually:
sudo apt-get install python3-tk

# Install pytesseract
pip install pytesseract

# Install Pillow for image processing
pip install Pillow

# Install fpdf to create PDF files
pip install fpdf

# Install python-docx to create Word documents
pip install python-docx
3. Install Tesseract-OCR Engine

Since pytesseract is only a wrapper, you also need to install the Tesseract-OCR engine:

  • Windows: Download from Tesseract GitHub and install.
  • Linux (Ubuntu/Debian): sudo apt install tesseract-ocr
  • macOS (using Homebrew): brew install tesseract
4. Set Tesseract Path in Python Code
pytesseract.pytesseract.tesseract_cmd = r"C:\Program Files\Tesseract-OCR\tesseract.exe"

Adjust the path if you installed Tesseract in a different location.

5. (Optional) Create a requirements.txt

To install all packages at once, you can create a requirements.txt file with the following content:

pytesseract
Pillow
fpdf
python-docx

Then run:

pip install -r requirements.txt

Now your Python environment is fully ready to build the project and extract text from images easily!

Project Overview: Extract the text From an image to pdf and word file

This simple Python application allows users to:

  1. Select an image (formats: PNG, JPG, BMP, TIFF)
  2. Extract text using OCR
  3. View extracted text in a scrollable text box
  4. Save extracted text as a Word (.docx) file
  5. Save extracted text as a PDF (.pdf) file

How It Works

1. Selecting an Image

Click the “Select Image” button to open a file dialog where you can choose an image file. The application supports PNG, JPG, BMP, and TIFF formats.

2. Extracting Text

After selecting an image, click “Extract Text”. The app uses Tesseract OCR to scan the image and retrieve all readable text, which is displayed inside a scrollable text widget.

3. Saving as Word or PDF

Once the text is extracted, you can choose to save it as a Word document or a PDF file. The application ensures Unicode font support for multi-language text.

Key Code Snippets


Selecting an Image File
filepath = filedialog.askopenfilename(
    filetypes=[("Image Files", "*.png *.jpg *.jpeg *.bmp *.tiff")]
)
Extracting Text Using pytesseract
img = Image.open(image_path)
text = pytesseract.image_to_string(img)
Saving Text as a Word Document
doc = Document()
doc.add_paragraph(text)
doc.save(filepath)
Saving Text as a PDF Document
pdf = FPDF()
pdf.add_page()
pdf.add_font('DejaVu', '', font_path, uni=True)
pdf.set_font('DejaVu', '', 12)
for line in text.split('\n'):
    pdf.multi_cell(0, 10, line)
pdf.output(filepath)
Benefits of Automating Text Extraction
  • Saves time compared to manual typing
  • Improves data entry accuracy
  • Streamlines document management
  • Boosts productivity for businesses and individuals

Complete Code extract-text.py

import tkinter as tk
from tkinter import filedialog, messagebox, ttk
from tkinter.scrolledtext import ScrolledText
from PIL import Image
import pytesseract
from docx import Document
from fpdf import FPDF
import os
import threading

# Set path to tesseract.exe
pytesseract.pytesseract.tesseract_cmd = r"C:\Program Files\Tesseract-OCR\tesseract.exe"  # Adjust if needed

def select_image():
    filepath = filedialog.askopenfilename(
        title="Select an image file",
        filetypes=[("Image Files", "*.png *.jpg *.jpeg *.bmp *.tiff")]
    )
    if filepath:
        entry_file.delete(0, tk.END)
        entry_file.insert(0, filepath)

def extract_text_threaded():
    threading.Thread(target=extract_text).start()

def extract_text():
    image_path = entry_file.get()
    if not os.path.exists(image_path):
        messagebox.showerror("Error", "Image file does not exist!")
        return

    try:
        progress_bar.place(relx=0.5, rely=0.5, anchor="center")  # Centered inside text box
        progress_bar.start()

        img = Image.open(image_path)
        text = pytesseract.image_to_string(img)

        if not text.strip():
            messagebox.showinfo("No Text", "No text detected in the image.")
            return

        text_box.config(state=tk.NORMAL)
        text_box.delete(1.0, tk.END)
        text_box.insert(tk.END, text)
        text_box.config(state=tk.NORMAL)

    except Exception as e:
        messagebox.showerror("Error", str(e))
    finally:
        progress_bar.stop()
        progress_bar.place_forget()  # Hide progress bar

def save_as_word():
    text = text_box.get(1.0, tk.END).strip()
    if not text:
        messagebox.showwarning("Warning", "No text to save!")
        return

    filepath = filedialog.asksaveasfilename(
        defaultextension=".docx",
        filetypes=[("Word Document", "*.docx")]
    )
    if filepath:
        try:
            doc = Document()
            doc.add_paragraph(text)
            doc.save(filepath)
            messagebox.showinfo("Saved", f"Text saved as Word file:\n{filepath}")
        except Exception as e:
            messagebox.showerror("Error", str(e))

def save_as_pdf():
    text = text_box.get(1.0, tk.END).strip()
    if not text:
        messagebox.showwarning("Warning", "No text to save!")
        return

    filepath = filedialog.asksaveasfilename(
        defaultextension=".pdf",
        filetypes=[("PDF file", "*.pdf")]
    )
    if filepath:
        try:
            pdf = FPDF()
            pdf.add_page()
            
            # Add a Unicode font (DejaVuSans.ttf must be in the same folder)
            font_path = os.path.join(os.path.dirname(__file__), r"D:\Python\extract-text\DejavuSans.ttf")
            pdf.add_font('DejaVu', '', font_path, uni=True)
            pdf.set_font('DejaVu', '', 12)
            
            for line in text.split('\n'):
                pdf.multi_cell(0, 10, line)

            pdf.output(filepath)
            messagebox.showinfo("Saved", f"Text saved as PDF file:\n{filepath}")
        except Exception as e:
            messagebox.showerror("Error", str(e))


# Helper function to get correct path for icon
def resource_path(relative_path):
    try:
        # PyInstaller creates a temp folder and stores path in _MEIPASS
        base_path = sys._MEIPASS
    except Exception:
        base_path = os.path.abspath(".")
    return os.path.join(base_path, relative_path)

# --- GUI ---
root = tk.Tk()
root.title("Image to Text Extractor")
root.minsize(400, 600)  # Minimum window size

frame = tk.Frame(root, padx=10, pady=10)
frame.pack(fill=tk.BOTH, expand=True)

# Widgets
btn_select = tk.Button(frame, text="Select Image", command=select_image)
entry_file = tk.Entry(frame, width=50)
btn_extract = tk.Button(frame, text="Extract Text", command=extract_text_threaded)

text_frame = tk.Frame(frame)
text_box = ScrolledText(text_frame, width=80, height=20, wrap=tk.WORD)

progress_bar = ttk.Progressbar(text_frame, orient=tk.HORIZONTAL, mode='indeterminate', length=200)
progress_bar.place_forget()

btn_save_word = tk.Button(frame, text="Save as Word", command=save_as_word)
btn_save_pdf = tk.Button(frame, text="Save as PDF", command=save_as_pdf)

# Layout function (dynamic)
def layout_widgets(event=None):
    width = root.winfo_width()

    # Clear old layout
    for widget in frame.winfo_children():
        widget.grid_forget()
        widget.pack_forget()

    if width < 500:  # Mobile Layout
        btn_select.pack(pady=5, fill=tk.X)
        entry_file.pack(pady=5, fill=tk.X)
        btn_extract.pack(pady=5, fill=tk.X)

        text_frame.pack(pady=10, fill=tk.BOTH, expand=True)
        text_box.pack(fill=tk.BOTH, expand=True)

        btn_save_word.pack(pady=5, fill=tk.X)
        btn_save_pdf.pack(pady=5, fill=tk.X)

    else:  # Desktop Layout
        btn_select.grid(row=0, column=0, padx=5, pady=5)
        entry_file.grid(row=0, column=1, padx=5, pady=5, sticky="ew")
        btn_extract.grid(row=0, column=2, padx=5, pady=5)

        frame.grid_columnconfigure(1, weight=1)

        text_frame.grid(row=1, column=0, columnspan=3, pady=10, sticky="nsew")
        text_box.pack(fill=tk.BOTH, expand=True)

        btn_save_word.grid(row=2, column=0, pady=5)
        btn_save_pdf.grid(row=2, column=2, pady=5)

    # Make sure frame expands correctly
    frame.pack(fill=tk.BOTH, expand=True)

# Bind resize event
root.bind("", layout_widgets)

# Initialize layout
layout_widgets()

root.mainloop()


Download Source Code

Conclusion

By completing this project, you now know how to Extract the text From an image to pdf and word file using a Python Tkinter GUI application. This hands-on project introduces you to OCR technology and file conversion, which are valuable skills for automating tasks and improving workflow efficiency. You can extend this app by adding features like multi-image processing or cloud uploads to make it even more powerful!

News API Integration In Android App Java

In today’s mobile-first world, delivering real-time content quickly and smoothly is crucial for user engagement. In this tutorial on News API Integration In Android App Java, you’ll learn how to fetch and display news articles in an Android app using Retrofit, RecyclerView, and ProgressBar, while implementing an infinite scroll feature — all written in Java.

By the end of this guide, you will have a working news app that fetches the latest articles based on a search query and loads more content as the user scrolls down.


🚀 Key Components We’ll Use

  • RecyclerView — To display news articles in a scrollable list.
  • ProgressBar — To show loading status while fetching data.
  • Retrofit — For efficient and safe API calls.
  • Infinite Scrolling — Load more news automatically when the user reaches the end.
  • Handler — To simulate slight loading delays during pagination.

🔧 Step-by-Step Implementation

1. Setting Up RecyclerView

First, we initialize our RecyclerView and set up a custom NewsAdapter to bind news articles dynamically.


private void setupRecyclerView() {
    recyclerView.setLayoutManager(new LinearLayoutManager(this));
    adapter = new NewsAdapter(this, listArticles, article -> {
        NewsDialogFragment dialogFragment = NewsDialogFragment.newInstance(article);
        dialogFragment.show(getSupportFragmentManager(), "NewsDialogFragment");
    });
    recyclerView.setAdapter(adapter);
}

Here, each news item is clickable and opens a DialogFragment to show article details.


2. Fetching News Using Retrofit

We use Retrofit to fetch news data asynchronously from an API. Here’s the main method:

🔔 Note: Make sure to replace API_KEY with your actual News API key to successfully fetch the news data.
You can get your API key from NewsAPI.org.

private void fetchNews(String query, int limit, int offset) {
    NewsApiService apiService = ApiClient.getRetrofitInstance().create(NewsApiService.class);

    Call<ArticlesResponse> call = apiService.getArticles(query, limit, offset, API_KEY);
    call.enqueue(new Callback<ArticlesResponse>() {
        @Override
        public void onResponse(@NonNull Call<ArticlesResponse> call, @NonNull Response<ArticlesResponse> response) {
            isLoading = false;
            adapter.removeLoadingFooter();
            if (response.isSuccessful() && response.body() != null) {
                List<ArticlesResponse.Article> newArticles = response.body().getArticles();
                if (newArticles != null && !newArticles.isEmpty()) {
                    int oldSize = listArticles.size();
                    listArticles.addAll(newArticles);
                    adapter.notifyItemRangeInserted(oldSize, newArticles.size());
                }
            }
            webProgress.setVisibility(View.GONE);
        }

        @Override
        public void onFailure(@NonNull Call<ArticlesResponse> call, @NonNull Throwable t) {
            webProgress.setVisibility(View.GONE);
            Toast.makeText(MainActivity.this, "Error: " + t.getMessage(), Toast.LENGTH_SHORT).show();
        }
    });
}

3. Adding Infinite Scroll (Load More News)

To enable infinite scrolling, we attach a scroll listener to the RecyclerView. When the user scrolls near the bottom, we load more news automatically.


recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
    @Override
    public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
        super.onScrolled(recyclerView, dx, dy);

        LinearLayoutManager layoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();
        int totalItemCount = Objects.requireNonNull(layoutManager).getItemCount();
        int lastVisibleItem = layoutManager.findLastVisibleItemPosition();

        if (!isLoading && lastVisibleItem == totalItemCount - 10) {
            isLoading = true;
            adapter.addLoadingFooter();

            new Handler().postDelayed(() -> {
                currentOffset += PAGE_SIZE;
                fetchNews(QUERY, PAGE_SIZE, currentOffset);
            }, 1000); // simulate network delay
        }
    }
});

✅ This ensures a seamless user experience without manual refresh!


📱 Full MainActivity Java Code

For your reference, here’s the complete MainActivity.java:


public class MainActivity extends AppCompatActivity {
    private ProgressBar webProgress;
    private RecyclerView recyclerView;
    private NewsAdapter adapter;
    private List listArticles = new ArrayList<>();
    private boolean isLoading = false;
    private int currentOffset = 0;
    private final int PAGE_SIZE = 20;
    private final String QUERY = "bitcoin"; // or anything you use

    private static final String API_KEY = "API_KEY";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        webProgress = findViewById(R.id.web_progress);
        webProgress.setVisibility(VISIBLE);

        recyclerView = findViewById(R.id.recycler_view);
        setupRecyclerView();

        fetchNews(QUERY, PAGE_SIZE, currentOffset);
    }


    private void fetchNews(String query, int limit, int offset) {
        NewsApiService apiService = ApiClient.getRetrofitInstance().create(NewsApiService.class);

        Call call = apiService.getArticles(query, limit, offset, API_KEY);
        call.enqueue(new Callback() {
            @Override
            public void onResponse(@NonNull Call call, @NonNull Response response) {

                isLoading = false;
                adapter.removeLoadingFooter();

                if (response.isSuccessful() && response.body() != null) {
                    List newArticles = response.body().getArticles();

                    if (newArticles != null && !newArticles.isEmpty()) {
                        int oldSize = listArticles.size();
                        listArticles.addAll(newArticles);

                        adapter.notifyItemRangeInserted(oldSize, newArticles.size());
                    }

                    NewsLog.INSTANCE.d("onResponse", "News Loaded");

                }else {

                    NewsLog.INSTANCE.d("onResponse", "Failed to load news");

                }

                webProgress.setVisibility(View.GONE);

            }

            @Override
            public void onFailure(@NonNull Call call, @NonNull Throwable t) {
                webProgress.setVisibility(View.GONE);
                Toast.makeText(MainActivity.this, "Error: " + t.getMessage(), Toast.LENGTH_SHORT).show();
                NewsLog.INSTANCE.d("onFailure", t.getMessage());
            }
        });
    }


    private void setupRecyclerView(){

        recyclerView.setLayoutManager(new LinearLayoutManager(this));
        adapter = new NewsAdapter(this, listArticles, article -> {
            // listen news click here, to open the news
            NewsDialogFragment dialogFragment = NewsDialogFragment.newInstance(article);
            dialogFragment.show(getSupportFragmentManager(), "NewsDialogFragment");

        });
        recyclerView.setAdapter(adapter);

        recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
                super.onScrolled(recyclerView, dx, dy);

                LinearLayoutManager layoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();
                int totalItemCount = Objects.requireNonNull(layoutManager).getItemCount();
                int lastVisibleItem = layoutManager.findLastVisibleItemPosition();

                if (!isLoading && lastVisibleItem == totalItemCount - 10) {
                    isLoading = true;
                    adapter.addLoadingFooter();

                    new Handler().postDelayed(() -> {
                        currentOffset += PAGE_SIZE;
                        fetchNews(QUERY, PAGE_SIZE, currentOffset);
                    }, 1000); // simulate delay

                }
            }
        });

    }

}

📱 Full NewsAdapter Java Code

For your reference, here’s the complete NewsAdapter.java:


public class NewsAdapter extends RecyclerView.Adapter {

    private static final int VIEW_TYPE_ITEM = 0;
    private static final int VIEW_TYPE_LOADING = 1;
    private List articles;
    private Context context;
    private boolean isLoadingAdded = false;
    private onNewsClickListener listener;

    public NewsAdapter(Context context, List articles, onNewsClickListener listener) {
        this.context = context;
        this.articles = articles;
        this.listener = listener;
    }

    @Override
    public int getItemViewType(int position) {
        return (position == articles.size() && isLoadingAdded) ? VIEW_TYPE_LOADING : VIEW_TYPE_ITEM;
    }

    @Override
    public int getItemCount() {
        return articles.size() + (isLoadingAdded ? 1 : 0);
    }

    @NonNull
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        if (viewType == VIEW_TYPE_ITEM) {
            View view = LayoutInflater.from(context).inflate(R.layout.item_article, parent, false);
            return new ArticleViewHolder(view);
        } else {
            View view = LayoutInflater.from(context).inflate(R.layout.item_loading, parent, false);
            return new LoadingViewHolder(view);
        }
    }

    @Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
        if (getItemViewType(position) == VIEW_TYPE_ITEM) {
            ArticleViewHolder viewHolder = (ArticleViewHolder) holder;
            ArticlesResponse.Article article = articles.get(position);
            viewHolder.title.setText(article.getTitle());
            viewHolder.description.setText(article.getDescription());
            // Add image loading if needed

            Glide.with(context)
                    .load(article.getFeedImage())  // Image URL or resource
                    .apply(new RequestOptions().placeholder(R.drawable.place_holder)
                          //  .error(R.drawable.error_image)
                    )
                    .into(((ArticleViewHolder) holder).imageView);  // The ImageView where the image will be loaded

            holder.itemView.setOnClickListener(v -> {
                int position1 = holder.getAdapterPosition();
                if(listener != null && position1 != RecyclerView.NO_POSITION){
                    listener.onClick(article);
                }
            });

        }
    }

    public void addLoadingFooter() {
        isLoadingAdded = true;
        notifyItemInserted(articles.size());
    }

    public void removeLoadingFooter() {
        if (isLoadingAdded) {
            isLoadingAdded = false;
            notifyItemRemoved(articles.size());
        }
    }

    static class ArticleViewHolder extends RecyclerView.ViewHolder {
        TextView title, description;
        ImageView imageView;

        public ArticleViewHolder(View itemView) {
            super(itemView);
            title = itemView.findViewById(R.id.article_title);
            description = itemView.findViewById(R.id.article_description);
            imageView = itemView.findViewById(R.id.iv_news);
        }
    }

    static class LoadingViewHolder extends RecyclerView.ViewHolder {
        public LoadingViewHolder(View itemView) {
            super(itemView);
        }
    }
}



Download Source Code

🎯 Final Thoughts

By combining Retrofit, RecyclerView, and ProgressBar, we’ve built a highly efficient, real-time news app that supports infinite scrolling in Android using Java.

This pattern is extremely useful for creating news apps, social media feeds, blogs, and any app that requires real-time content loading.

Pro Tip: Always handle error cases smartly (like no internet, API limit errors) to enhance user experience even further.

Barcode Scanner Invoice Generator App in Android Java

Introduction
In this tutorial, we will explore how to develop a Barcode Scanner Invoice Generator App in Android Java that allows users to scan barcodes, add products to a list, and generate a PDF invoice. This app is ideal for small businesses or retail stores that need a quick and efficient way to manage transactions. By the end of this guide, you’ll be able to build a fully functional barcode scanner invoice generator app using modern Android development tools.


Prerequisites

Before getting started, ensure you have the following:

  • Android Studio installed
  • Basic knowledge of Java/Kotlin
  • Dependencies for barcode scanning and PDF generation

Step 1: Adding Dependencies

To enable barcode scanning and PDF generation, add the following dependencies to your build.gradle file:

implementation 'com.google.mlkit:barcode-scanning:17.2.0'
implementation 'com.itextpdf:itext7-core:7.1.15'

Step 2: Implement Barcode Scanner

To scan barcodes, use the Google ML Kit. Integrate the camera preview and process the scanned barcode:

BarcodeScannerOptions options =
    new BarcodeScannerOptions.Builder()
        .setBarcodeFormats(Barcode.FORMAT_ALL_FORMATS)
        .build();

BarcodeScanner scanner = BarcodeScanning.getClient(options);

Once a barcode is scanned, retrieve the product details and add them to a list.


Step 3: Displaying Items in RecyclerView

Create a RecyclerView adapter to show scanned products dynamically:

public class ProductAdapter extends RecyclerView.Adapter<ProductAdapter.ViewHolder> {
    private List<Product> productList;
    
    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
        Product product = productList.get(position);
        holder.name.setText(product.getName());
        holder.price.setText(String.valueOf(product.getPrice()));
    }
}

Step 4: Generating a PDF Invoice

Use iText7 to create a PDF file containing the product details and save it to device storage:

PdfWriter writer = new PdfWriter(filePath);
PdfDocument pdfDoc = new PdfDocument(writer);
Document document = new Document(pdfDoc);
document.add(new Paragraph("Invoice"));

for (Product product : productList) {
    document.add(new Paragraph(product.getName() + " - " + product.getPrice()));
}
document.close();

Complete Code Here


1. MainActivity.java


public class MainActivity extends AppCompatActivity {

    Button btnCreatePDf;
    Bitmap bitmap, scaledBitmap;
    EditText etCustomerName;
    private final String[] informationArray = new String[]{"Name", "Company Name", "Address", "Phone", "Email"};

    private int srNumber;
    String ItemName, price, Quantity, priceTotal;


    private RecyclerView recyclerView;

    private MyAdapter adapter;
    private final List listItem = new ArrayList<>();
    private final List fullCodeList = new ArrayList<>();


    private static final int CAMERA_PERMISSION_REQUEST_CODE = 200;


    private DecoratedBarcodeView barcodeView;

    private final Map<String, String> map = new HashMap<>();

    int startingIndex = 5;
    String copyDecodeText;
    int yAxisForValue = 280;
    private List totalPricePerItemList = new ArrayList<>();
    private String customerName;

    String pattern = "^[-+]?[\\d&]*\\.?\\d+$";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        EdgeToEdge.enable(this);
        setContentView(R.layout.activity_main);
        ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
            Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
            v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
            return insets;
        });
        etCustomerName = findViewById(R.id.etCustomerName);
        btnCreatePDf = findViewById(R.id.btnNext);
        bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.logo_2);

        recyclerView = findViewById(R.id.myRecyclerView);
        barcodeView = findViewById(R.id.zxingBarcodeScanner);
        barcodeView.setStatusText("");

        recyclerView.setLayoutManager(new LinearLayoutManager(this));
        adapter = new MyAdapter(this, listItem, (position, quantity) -> {
            listItem.get(position).setItemQuantity(quantity);
        });
        recyclerView.setAdapter(adapter);

        map.put("1244", "Cold Drink");
        map.put("0054", "Burger");
        map.put("0187", "Sandwich");
        map.put("7176", "Pizza");

        startScanning();
        //scaledBitmap = Bitmap.createScaledBitmap(bitmap,100,100,false);
        ActivityCompat.requestPermissions(this, new String[]{
                Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.CAMERA}, PackageManager.PERMISSION_GRANTED);
        {

            //  createPdf();
            startScanning();
            createPdf();
        }

    }


    private void startScanning() {
        barcodeView.decodeContinuous(new BarcodeCallback() {
            @Override
            public void barcodeResult(BarcodeResult result) {
                // Handle the decoded result
                String decodedText = result.getText();
                // addItemToRecyclerView(decodedText);

                if (decodedText.length() == 14) {
                    // Check if barcode has been scanned before
                    if (!fullCodeList.contains(decodedText)) {

                        fullCodeList.add(decodedText);

                        copyDecodeText = decodedText;

                        // Extract the last 4 digits using substring
                        double lastDigitPrice = Double.parseDouble(decodedText.substring(decodedText.length() - 4));

                        String productId = copyDecodeText.substring(startingIndex, startingIndex + 4);

                        if (String.valueOf(lastDigitPrice).matches(pattern) && productId.matches(pattern)) {
                            ItemModel itemModel = new ItemModel(lastDigitPrice, map.get(productId), 1);
                            addItemToRecyclerView(itemModel);
                        }
                    }
                }

            }


            @Override
            public void possibleResultPoints(List resultPoints) {
                // You can use this callback method to show visual cues on the viewfinder.
            }


        });
    }


    private void addItemToRecyclerView(ItemModel item) {
        //  listItem.add(item);
        //   adapter.addItem(item);
        listItem.add(item);
        adapter.notifyItemInserted(listItem.size() - 1);
    }


    private void createPdf() {
        btnCreatePDf.setOnClickListener(v -> {

            if(etCustomerName.getText().length() != 0 && adapter.getItemCount() != 0){

                customerName = etCustomerName.getText().toString();

                PdfDocument pdfDocument = new PdfDocument();
                Paint paint = new Paint();
                //    paint.setLetterSpacing(0.01f);

                PdfDocument.PageInfo pageInfo1 = new PdfDocument.PageInfo.Builder(595, 842, 1).create();
                PdfDocument.Page myPage1 = pdfDocument.startPage(pageInfo1);
                Canvas canvas = myPage1.getCanvas();


                // insert the picture
                int endPosition = pageInfo1.getPageWidth() - 100;
                scaledBitmap = Bitmap.createScaledBitmap(bitmap, 100, 100, false);

                canvas.drawBitmap(scaledBitmap, endPosition, 0, paint);

                // draw a text as like invoice
                paint.setTextAlign(Paint.Align.CENTER);
                paint.setTextSize(32.0f);
                paint.setColor(ContextCompat.getColor(this, R.color.green));
                paint.setTypeface(Typeface.defaultFromStyle(Typeface.BOLD));
                canvas.drawText("Invoice", (float) pageInfo1.getPageWidth() / 2, 100, paint);


                // customer name headiing
                paint.setTextAlign(Paint.Align.LEFT);
                paint.setTextSize(10.0f);
                paint.setColor(Color.BLACK);
                paint.setTypeface(Typeface.defaultFromStyle(Typeface.ITALIC));
                canvas.drawText("Customer Name: "+customerName, 80, 170, paint);


          /*  int endX = pageInfo1.getPageWidth() - 5;

            float textWidth = paint.measureText(getCurrentDate());
            float x = pageInfo1.getPageWidth() - textWidth;*/

                // date and time heading
                paint.setTextAlign(Paint.Align.RIGHT);
                canvas.drawText("Date: " + getCurrentDate(), 590, 170, paint);
                canvas.drawText("Time: " + getCurrentTime(), 590, 180, paint);

                //draw rectangle stroke etc
                paint.setTextAlign(Paint.Align.RIGHT);
                paint.setStyle(Paint.Style.STROKE);
                paint.setStrokeWidth(1);
                //draw rectangle
                canvas.drawRect(10, 220, pageInfo1.getPageWidth() - 10, 250, paint);
                // draw four line vertical, to make portions in rectangle
                canvas.drawLine(100, 220, 100, 250, paint);
                canvas.drawLine(300, 220, 300, 250, paint);
                canvas.drawLine(430, 220, 430, 250, paint);
                canvas.drawLine(500, 220, 500, 250, paint);


                paint.setStrokeWidth(0);
                paint.setStyle(Paint.Style.FILL);
                paint.setTextSize(12.0f);
                paint.setTextAlign(Paint.Align.LEFT);
                paint.setTypeface(Typeface.defaultFromStyle(Typeface.BOLD));

                // draw texts invoice data headings
                canvas.drawText("Sr No.", 15, 240, paint);
                canvas.drawText("Item Name", 105, 240, paint);
                canvas.drawText("Price", 305, 240, paint);
                canvas.drawText("Qty", 435, 240, paint);
                canvas.drawText("Total", 505, 240, paint);

                srNumber = 0;
                for (int sNo = 0; sNo < listItem.size(); sNo++) { srNumber++; // draw values as like qty price item name etc canvas.drawText(srNumber + "", 17, yAxisForValue, paint); canvas.drawText(listItem.get(sNo).getItemName(), 106, yAxisForValue, paint); canvas.drawText(String.valueOf(listItem.get(sNo).getItemPrice()), 306, yAxisForValue, paint); canvas.drawText(String.valueOf(listItem.get(sNo).getItemQuantity()), 435, yAxisForValue, paint); canvas.drawText(calculateTotalPerItem(listItem.get(sNo).getItemPrice(), listItem.get(sNo).getItemQuantity()), 508, yAxisForValue, paint); yAxisForValue += 20; } yAxisForValue += 20; // draw line below of value and above of total canvas.drawLine(300, yAxisForValue, pageInfo1.getPageWidth() - 10, yAxisForValue, paint); yAxisForValue += 20; // draw texts as like total canvas.drawText("Sub Total", 306, yAxisForValue, paint); canvas.drawText(String.valueOf(calculateSubTotal()), 508, yAxisForValue, paint); canvas.drawText(":", 435, yAxisForValue, paint); yAxisForValue += 20; canvas.drawText("Tax (5%)", 306, yAxisForValue, paint); canvas.drawText(String.valueOf(calculateTax()), 508, yAxisForValue, paint); canvas.drawText(":", 435, yAxisForValue, paint); Paint paint2 = new Paint(); paint2.setStrokeWidth(1); // Set the stroke width to 5 (adjust as needed) paint2.setColor(ContextCompat.getColor(this, R.color.black)); // Set the stroke color to black paint2.setStyle(Paint.Style.STROKE); // Set the style to fill and stroke yAxisForValue += 35; // this value uses for top int bottom = yAxisForValue + 45; paint.setColor(ContextCompat.getColor(this, R.color.green)); canvas.drawRect(300, yAxisForValue, pageInfo1.getPageWidth() - 10, bottom, paint); canvas.drawRect(300, yAxisForValue, pageInfo1.getPageWidth() - 10, bottom, paint2); int yValueForGrandTotal = bottom - yAxisForValue; int calc = yValueForGrandTotal / 3; int finalCalc = calc * 2; yAxisForValue += finalCalc; paint.setColor(Color.WHITE); paint.setTypeface(Typeface.defaultFromStyle(Typeface.BOLD)); paint.setTextSize(16.0f); canvas.drawText("Total", 320, yAxisForValue, paint); canvas.drawText(String.valueOf(calculateSubTotal() + calculateTax()), 515, yAxisForValue, paint); paint.setColor(ContextCompat.getColor(this, R.color.black)); paint.setTextAlign(Paint.Align.LEFT); paint.setTextSize(8.0f); canvas.drawText("Invoice Number: " + System.currentTimeMillis(), 17, pageInfo1.getPageHeight() - 20, paint); paint.setTextAlign(Paint.Align.RIGHT); canvas.drawText("Generate by Al Saeed", pageInfo1.getPageWidth() - 17, pageInfo1.getPageHeight() - 20, paint); pdfDocument.finishPage(myPage1); String folderName = "AlsaeedFolder"; // Define your custom folder name File customFolder = new File(Environment.getExternalStorageDirectory(), folderName); if (!customFolder.exists()) { customFolder.mkdirs(); // Create the folder if it doesn't exist } File file = new File(customFolder, "myPDF.pdf"); try { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                        pdfDocument.writeTo(Files.newOutputStream(file.toPath()));
                    } else {
                        pdfDocument.writeTo(new FileOutputStream(file));
                    }
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }

                Toast.makeText(this, "File save in " + file.getAbsolutePath(), Toast.LENGTH_SHORT).show();
                // pdfDocument.close();


                // Open the PDF file using a PDF viewer app
                Intent intent = new Intent(Intent.ACTION_VIEW);
                Uri pdfUri = FileProvider.getUriForFile(this, "alsaeeddev.com.fileProvider", file);
                intent.setDataAndType(pdfUri, "application/pdf");
                intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); // Grant read permissions

                try {
                    startActivity(intent); // Launch the PDF viewer activity
                } catch (ActivityNotFoundException e) {
                    // Handle the case where no PDF viewer app is available
                    Toast.makeText(getApplicationContext(), "No PDF viewer app found", Toast.LENGTH_SHORT).show();
                }

                Toast.makeText(this, "File saved in " + file.getAbsolutePath(), Toast.LENGTH_SHORT).show();
                pdfDocument.close();

            }else {
                etCustomerName.setError("Enter Name");
            }




        });
    }


    private String getCurrentDate() {
        // Get the current date
        Calendar calendar = Calendar.getInstance();
        int year = calendar.get(Calendar.YEAR);
        int month = calendar.get(Calendar.MONTH) + 1; // Month is zero-based, so add 1
        int day = calendar.get(Calendar.DAY_OF_MONTH);

// Construct the date string
        return year + "-" + month + "-" + day;

    }


    private String getCurrentTime() {
        // Get the current time
        Calendar calendar = Calendar.getInstance();
        int hour = calendar.get(Calendar.HOUR_OF_DAY); // 24-hour format
        int minute = calendar.get(Calendar.MINUTE);
        int second = calendar.get(Calendar.SECOND);

// Construct the time string
        return hour + ":" + minute + ":" + second;
    }


    private String calculateTotalPerItem(double price, int quantity) {
        double total = price * quantity;
        totalPricePerItemList.add(total);
        return String.valueOf(total);
    }


    private double calculateSubTotal() {
        double subTotal = 0;
        for (int i = 0; i < totalPricePerItemList.size(); i++) {
            subTotal += totalPricePerItemList.get(i);
        }
        return subTotal;
    }


    private double calculateTax() {

        return calculateSubTotal() * 0.5;
    }

    @Override
    protected void onResume() {
        super.onResume();
        barcodeView.resume();
    }

    @Override
    protected void onPause() {
        super.onPause();
        yAxisForValue = 280;
        totalPricePerItemList.clear();
        barcodeView.pause();

    }

    @Override
    protected void onStop() {
        yAxisForValue = 280;
        totalPricePerItemList.clear();
        super.onStop();
    }

    @Override
    protected void onDestroy() {
        listItem.clear();
        fullCodeList.clear();
        super.onDestroy();
    }
}
</pre

2. MyAdapter.java

 


public class MyAdapter extends RecyclerView.Adapter {
    private final List itemList;

   private final Context context;
  private final QuantityChangeListener listener;

    public MyAdapter(Context context, List data, QuantityChangeListener listener) {
        this.itemList = data;
       this.listener = listener;
       this.context = context;
    }

    @NonNull
    @Override
    public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.rv_item, parent, false);
        return new MyViewHolder(view);
    }

    @Override
    public void onBindViewHolder(@NonNull MyViewHolder holder, int position) {
        holder.bindData(position);

      //  holder.itemView.setOnClickListener(v -> Toast.makeText(context, itemList.get(position).getItemQuantity(), Toast.LENGTH_SHORT).show());
    }

    @Override
    public int getItemCount() {
        return itemList.size();
    }

    public void addItem(String item) {
      /*  mData.add(item);
        notifyItemInserted(mData.size() - 1);*/

     /*   mData.add(0, item); // Insert item at index 0
        notifyItemInserted(0);*/
    }

    public  class MyViewHolder extends RecyclerView.ViewHolder {
        TextView itemName, itemPrice;
        EditText editText;


        public MyViewHolder(@NonNull View itemView) {
            super(itemView);
            itemName = itemView.findViewById(R.id.tvItemName);
            itemPrice = itemView.findViewById(R.id.tvItemPrice);
            editText = itemView.findViewById(R.id.etQuantity);


        }

        public void bindData(int position) {
            ItemModel item = itemList.get(position);
            itemName.setText(item.getItemName());
           itemPrice.setText(String.valueOf(item.getItemPrice()));
           editText.setText(String.valueOf(item.getItemQuantity()));

           editText.addTextChangedListener(new TextWatcher() {
               @Override
               public void beforeTextChanged(CharSequence s, int start, int count, int after) {

               }

               @Override
               public void onTextChanged(CharSequence s, int start, int before, int count) {
                   if(!TextUtils.isEmpty(s.toString())){
                       int quantity = Integer.parseInt(s.toString());
                       if(quantity != 0) {
                           item.setItemQuantity(quantity);
                           //   if(listener != null && position != RecyclerView.NO_POSITION) {
                           listener.onQuantityChanged(position, quantity);
                       }
                     //  }
                   }
               }

               @Override
               public void afterTextChanged(Editable s) {

               }
           });





        }

   /*     public interface QuantityChangeListener {
            void onQuantityChanged(int position, int quantity);
        }

        public void setListener(QuantityChangeListener listener){
            listener = listener;
        }*/


    }

}
</pre

3. QuantityChangeListener.java

 


public interface QuantityChangeListener {
    void onQuantityChanged(int position, int quantity);
}
</pre

4. ItemModel.java

 


public class ItemModel {
    private final double itemPrice;
    private final String itemName;
    private int itemQuantity;

    public int getItemQuantity() {
        return itemQuantity;
    }

    public void setItemQuantity(int quantity){
        this.itemQuantity = quantity;
    }



    public double getItemPrice() {
        return itemPrice;
    }

    public String getItemName() {
        return itemName;
    }


    public ItemModel(double itemPrice, String itemName, int itemQuantity) {
        this.itemPrice = itemPrice;
        this.itemName = itemName;
        this.itemQuantity = itemQuantity;
    }




}
</pre

5. activity_main.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"
    android:elevation="10dp"
    android:id="@+id/main"
    tools:context=".MainActivity">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/myRecyclerView"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_marginStart="1dp"
        android:layout_marginTop="8dp"
        android:layout_marginEnd="1dp"
        app:layout_constraintBottom_toTopOf="@id/btnNext"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        android:layout_marginBottom="8dp"
        app:layout_constraintTop_toBottomOf="@+id/zxingBarcodeScanner" />

    <com.journeyapps.barcodescanner.DecoratedBarcodeView
        android:id="@+id/zxingBarcodeScanner"
        android:layout_width="0dp"
        android:layout_height="150dp"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        android:layout_marginEnd="8dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/etCustomerName" />

 

    <androidx.appcompat.widget.AppCompatButton
        android:id="@+id/btnNext"
        android:layout_width="200dp"
        android:layout_height="wrap_content"
        android:text="Generate Invoice"
        android:textAllCaps="false"
        android:background="@drawable/btn_bg"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        android:layout_marginBottom="8dp"
        app:layout_constraintStart_toStartOf="parent" />

    <EditText
        android:id="@+id/etCustomerName"
        android:layout_width="0dp"
        android:layout_height="48dp"
        android:layout_marginStart="8dp"
        android:layout_marginTop="8dp"
        android:layout_marginEnd="8dp"
        android:background="@drawable/btn_bg"
        android:ems="10"
        android:textSize="14sp"
        android:paddingStart="8dp"
        android:paddingEnd="8dp"
        android:inputType="text"
        android:hint="Customer name"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

6. rv_item.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="wrap_content">

    <androidx.cardview.widget.CardView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        android:layout_marginEnd="8dp"
        android:layout_marginStart="8dp"
        android:layout_marginTop="5dp"
        android:elevation="8dp"
        android:backgroundTint="#E7EFE6"
        app:cardCornerRadius="8dp"
        app:layout_constraintTop_toTopOf="parent">

        <androidx.constraintlayout.widget.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent">

            <TextView
                android:id="@+id/tvItemName"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_marginStart="8dp"
                android:layout_marginTop="8dp"
                android:padding="3dp"
                android:text="Pepsi"
                android:textColor="@color/black"
                android:textStyle="bold"
                app:layout_constraintEnd_toStartOf="@+id/etQuantity"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toTopOf="parent" />

            <TextView
                android:id="@+id/tvItemPrice"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_marginEnd="8dp"
                android:layout_marginBottom="8dp"
                android:padding="3dp"
                android:text="$8"
                android:layout_marginTop="3dp"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintTop_toBottomOf="@+id/tvItemName" />

            <TextView
                android:id="@+id/textView"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_marginEnd="5dp"
                android:layout_marginBottom="8dp"
                android:padding="3dp"
                android:text="Price"
                android:layout_marginTop="3dp"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintEnd_toStartOf="@+id/tvItemPrice"
                app:layout_constraintTop_toBottomOf="@+id/tvItemName" />

            <EditText
                android:id="@+id/etQuantity"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:background="@android:color/transparent"
                android:text="1"
                android:layout_marginEnd="8dp"
                android:paddingStart="8dp"
                android:paddingEnd="8dp"
                android:ems="4"
                android:gravity="center"
                app:layout_constraintBottom_toBottomOf="@+id/tvItemName"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintTop_toTopOf="@+id/tvItemName" />

        </androidx.constraintlayout.widget.ConstraintLayout>

    </androidx.cardview.widget.CardView>

</androidx.constraintlayout.widget.ConstraintLayout>


Download Source Code

Conclusion

By following these steps, you can build a functional Android application that scans barcodes, lists products in a RecyclerView, and generates a PDF invoice. This project is highly useful for businesses looking for an easy invoicing system within their Android app.

📱 Android Image Compressor App with Image Picker and Storage

If you’re looking to build an Android Image Compressor App with Image Picker that lets users select, compress, and save images directly to their device, you’re in the right place. In this tutorial, we’ll walk you through how to create an Android image compression tool using Java, ActivityResultLauncher, SeekBar, and a multithreaded approach for performance.

🔧 What This App Does

  • Select images using the modern Image Picker API
  • Display original and compressed images
  • Adjust compression quality via SeekBar
  • Save compressed images to external storage
  • Optimize image processing using ExecutorService

🚀 Step-by-Step Breakdown

1. Image Selection Using ActivityResultLauncher

The app uses ActivityResultContracts.PickVisualMedia() to allow users to select an image from their gallery. Once an image is picked, it’s displayed in the original image view using:

 pickMultipleMedia.launch(new PickVisualMediaRequest.Builder()
    .setMediaType(ActivityResultContracts.PickVisualMedia.ImageOnly.INSTANCE)
    .build());

2. Live Compression Quality Adjustment

A SeekBar allows users to set the compression level in real-time. The compressionQuality variable updates with each user interaction, and the value is shown dynamically using a TextView.

 tvSeekValue.setText("Compression: " + progress + "%");

3. Image Compression in Background Threads

To keep the UI responsive, the app uses ExecutorService to handle compression on a background thread. This ensures the main thread isn’t blocked while processing images.

 Bitmap compressedBitmap = compressImage(imageModel.getOriginalImage(), compressionQuality);

4. Saving Compressed Images

Once an image is compressed, users can tap a button to save it into the Pictures/CompressedImages folder. The file is written using FileOutputStream, and then registered in the device gallery.

 addImageToGallery(file);

5. Displaying the Result

The app shows both the original and compressed image side by side, allowing users to visually compare them. A progress bar is used to indicate when compression is happening in the background.

🧠 Key Concepts Used

  • Bitmap Compression with Bitmap.compress()
  • Multithreading with Executors.newFixedThreadPool()
  • Modern Media Picker with ActivityResultContracts
  • Storage Access with Environment.getExternalStoragePublicDirectory()
  • Gallery Update via MediaStore

🛡️ Final Touch: Cleanup

Always remember to shut down the ExecutorService when the activity is destroyed to prevent memory leaks:

 @Override
protected void onDestroy() {
    super.onDestroy();
    executorService.shutdown();
}

Complete Code Here


1. MainActivity.java


public class MainActivity extends AppCompatActivity {


    private Button btnImagesSelect, btnSaveImages, btnCompressImages;
    private TextView tvSeekValue;
    private SeekBar compressionSeekBar;
    private int compressionQuality = 100;  // Default to 100%
    ImageModel imageModel;
    private ImageView originalImageView, compressedImageView;
    private ProgressBar progressBar;

    private final ExecutorService executorService = Executors.newFixedThreadPool(2);

    ActivityResultLauncher pickMultipleMedia =
            registerForActivityResult(new ActivityResultContracts.PickVisualMedia(), uri -> {


                if (uri != null) {
                    // Handle the selected media URI (image or video)
                    Log.d("Selected URI", uri.toString());
                    setImageInImageView(uri);
                } else {
                    Log.d("PickMedia", "No media selected");
                }


            });

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);


        originalImageView = findViewById(R.id.originalImageView);
        compressedImageView = findViewById(R.id.compressedImageView);
        btnImagesSelect = findViewById(R.id.selectImagesButton);
        btnSaveImages = findViewById(R.id.saveImagesButton);
        btnCompressImages = findViewById(R.id.compressImages);
        compressionSeekBar = findViewById(R.id.compressionSeekBar);
        tvSeekValue = findViewById(R.id.tvSeekValue);
        progressBar = findViewById(R.id.progressBar);


        btnImagesSelect.setOnClickListener(view -> openImagePicker());
        btnSaveImages.setOnClickListener(view -> saveCompressedImages());

        btnCompressImages.setOnClickListener(v -> compressImagesInBackground());

        compressionSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
            @Override
            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
                compressionQuality = progress;
                tvSeekValue.setText("Compression: " + progress + "%");
            }

            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {
            }

            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {
            }
        });
    }


    // open image picker to pick the image
    private void openImagePicker() {
        pickMultipleMedia.launch(new PickVisualMediaRequest.Builder()
                .setMediaType(ActivityResultContracts.PickVisualMedia.ImageOnly.INSTANCE)
                .build());
    }



    // set the selected image in the original image view
    private void setImageInImageView(Uri uri) {
        executorService.execute(() -> {
            try {
                InputStream inputStream = getContentResolver().openInputStream(uri);
                Bitmap originalBitmap = BitmapFactory.decodeStream(inputStream);
                imageModel = new ImageModel(originalBitmap, uri);


                runOnUiThread(() -> {
                    originalImageView.setImageBitmap(originalBitmap);
                    compressedImageView.setImageBitmap(null);

                });
            } catch (IOException e) {
                e.printStackTrace();
            }
        });
    }



    // compressed image in the background
    private void compressImagesInBackground() {
        progressBar.setVisibility(View.VISIBLE);
        executorService.execute(() -> {

            Bitmap compressedBitmap = compressImage(imageModel.getOriginalImage(), compressionQuality);
            imageModel.setCompressedImage(compressedBitmap);

            runOnUiThread(() -> {
                compressedImageView.setImageBitmap(compressedBitmap);
                progressBar.setVisibility(View.GONE);
            });

        });
    }


    //compress image method, which is calling in the compressImagesInBackground method
    private Bitmap compressImage(Bitmap original, int quality) {
        ByteArrayOutputStream stream = new ByteArrayOutputStream();
        original.compress(Bitmap.CompressFormat.JPEG, quality, stream);
        byte[] byteArray = stream.toByteArray();
        return BitmapFactory.decodeByteArray(byteArray, 0, byteArray.length);
    }



    // save the compressed image in the phone storage
    private void saveCompressedImages() {
        executorService.execute(() -> {
            File directory = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), "CompressedImages");
            if (!directory.exists()) {
                directory.mkdirs();
            }

            File file = new File(directory, "compressed_" + System.currentTimeMillis() + ".jpg");
            try (FileOutputStream fos = new FileOutputStream(file)) {
                imageModel.getCompressedImage().compress(Bitmap.CompressFormat.JPEG, 100, fos);
                fos.flush();
                addImageToGallery(file);
            } catch (IOException e) {
                e.printStackTrace();
            }

            // Show toast with full image path
            runOnUiThread(() -> Toast.makeText(
                    this,
                    "Image saved at:\n" + file.getAbsolutePath(),
                    Toast.LENGTH_LONG
            ).show());
        });
    }


    //show the image in the gallery
    private void addImageToGallery(File file) {
        ContentValues values = new ContentValues();
        values.put(MediaStore.Images.Media.DATA, file.getAbsolutePath());
        values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg");
        getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        executorService.shutdown();
    }
}

📦 2. ImageModel.java – Custom Model Class for Handling Images

💡 Note: This is a reusable model class used to store and manage both the original and compressed image Bitmaps, along with the image URI. While it’s helpful for clean code and scalability, you can also work without a model class if you prefer a simpler implementation.


public class ImageModel {
    private Bitmap originalImage;
    private Bitmap compressedImage;
    private Uri imageUri;

    public ImageModel(Bitmap originalImage, Uri imageUri) {
        this.originalImage = originalImage;
        this.imageUri = imageUri;
        this.compressedImage = originalImage;
    }

    public Bitmap getOriginalImage() {
        return originalImage;
    }

    public Bitmap getCompressedImage() {
        return compressedImage;
    }

    public void setCompressedImage(Bitmap compressedImage) {
        this.compressedImage = compressedImage;
    }

    public Uri getImageUri() {
        return imageUri;
    }
}

3. activity_main.xml

 <ScrollView 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:fillViewport="true"
    android:orientation="vertical"
    android:padding="16dp"
    tools:context=".MainActivity">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <TextView
            android:id="@+id/tvOriginal"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginTop="8dp"
            android:gravity="center"
            android:text="Original Image"
            android:textColor="@color/black"
            android:textSize="16sp"
            android:textStyle="bold"
            app:layout_constraintBottom_toTopOf="@id/cvOriginal"
            app:layout_constraintEnd_toEndOf="@id/cvOriginal"
            app:layout_constraintStart_toStartOf="@id/cvOriginal"
            app:layout_constraintTop_toTopOf="parent" />

        <TextView
            android:id="@+id/tvCompressed"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="8dp"
            android:gravity="center"
            android:text="Compressed Image"
            android:textColor="@color/black"
            android:textSize="16sp"
            android:textStyle="bold"
            app:layout_constraintBottom_toTopOf="@id/cvCompressed"
            app:layout_constraintEnd_toEndOf="@id/cvCompressed"
            app:layout_constraintStart_toStartOf="@id/cvCompressed"
            app:layout_constraintTop_toTopOf="parent" />

        <androidx.cardview.widget.CardView
            android:id="@+id/cvOriginal"
            android:layout_width="0dp"
            android:layout_height="300dp"
            app:cardCornerRadius="8dp"
            app:layout_constraintBottom_toTopOf="@+id/compressionSeekBar"
            app:layout_constraintEnd_toStartOf="@+id/cvCompressed"
            app:layout_constraintHorizontal_bias="0.5"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent">

            <ImageView
                android:id="@+id/originalImageView"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:scaleType="centerCrop" />

        </androidx.cardview.widget.CardView>

        <androidx.cardview.widget.CardView
            android:id="@+id/cvCompressed"
            android:layout_width="0dp"
            android:layout_height="300dp"
            android:layout_marginStart="16dp"
            app:cardCornerRadius="8dp"
            app:layout_constraintBottom_toTopOf="@+id/compressionSeekBar"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintHorizontal_bias="0.5"
            app:layout_constraintStart_toEndOf="@+id/cvOriginal"
            app:layout_constraintTop_toTopOf="parent">

            <ImageView
                android:id="@+id/compressedImageView"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:scaleType="centerCrop" />

        </androidx.cardview.widget.CardView>

        <ProgressBar
            android:id="@+id/progressBar"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:visibility="gone"
            app:layout_constraintBottom_toBottomOf="@id/cvCompressed"
            app:layout_constraintEnd_toEndOf="@id/cvCompressed"
            app:layout_constraintStart_toStartOf="@id/cvCompressed"
            app:layout_constraintTop_toTopOf="@id/cvCompressed" />

        <SeekBar
            android:id="@+id/compressionSeekBar"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginBottom="8dp"
            android:max="100"
            android:progress="100"
            app:layout_constraintBottom_toTopOf="@+id/selectImagesButton"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@id/cvCompressed" />

        <TextView
            android:id="@+id/tvSeekValue"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal"
            android:layout_marginTop="8dp"
            android:layout_marginBottom="8dp"
            android:text="100%"
            app:layout_constraintBottom_toTopOf="@id/selectImagesButton"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="@id/compressionSeekBar"
            app:layout_constraintTop_toBottomOf="@id/compressionSeekBar" />

        <Button
            android:id="@+id/selectImagesButton"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginBottom="8dp"
            android:text="Select Image"
            app:layout_constraintBottom_toTopOf="@+id/compressImages"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent" />

        <Button
            android:id="@+id/compressImages"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginBottom="8dp"
            android:text="Compress Image"
            app:layout_constraintBottom_toTopOf="@+id/saveImagesButton"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent" />

        <Button
            android:id="@+id/saveImagesButton"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginBottom="16dp"
            android:text="Save Compressed Image"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent" />

    </androidx.constraintlayout.widget.ConstraintLayout>

</ScrollView>


Download Source Code

💡 Final Thoughts

This Android image compressor app provides a practical implementation of media handling, compression, and storage. Whether you’re building a photo editing app or simply want to reduce image size before upload, this codebase gives you a solid foundation to build on.

Simple Web Browser in Android Using WebView in Java

Looking to create a Simple Web Browser in Android Using WebView in Java that mimics Google Chrome? In this tutorial, we’ll walk you through building a full-featured, modern web browser in Java using Android Studio. This browser includes essential features like web page loading, navigation buttons, progress bar, and URL input with “GO” button support — all wrapped in a sleek UI similar to Chrome.

🔧 Features of This Android Web Browser

  • URL input bar with IME GO button handling
  • Full WebView support with JavaScript enabled
  • Progress bar to indicate page loading status
  • Back and Forward navigation buttons
  • Proper back press handling using OnBackPressedDispatcher

📱 MainActivity.java – Complete Code Overview

The heart of our browser lies in the MainActivity.java. Here’s what each component does:

public class MainActivity extends AppCompatActivity {

    private WebView webView;
    private EditText urlInput;
    private ProgressBar progressBar;
    private ImageButton backButton, forwardButton;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        getOnBackPressedDispatcher().addCallback(this, backPressedCallback);

        urlInput = findViewById(R.id.urlInput);
        webView = findViewById(R.id.webView);
        progressBar = findViewById(R.id.progressBar);
        backButton = findViewById(R.id.backButton);
        forwardButton = findViewById(R.id.forwardButton);

        // Enable JavaScript
        webView.getSettings().setJavaScriptEnabled(true);

        // Handle page load progress
        webView.setWebChromeClient(new WebChromeClient() {
            public void onProgressChanged(WebView view, int progress) {
                progressBar.setProgress(progress);
                progressBar.setVisibility(progress == 100 ? View.GONE : View.VISIBLE);
            }
        });

        // Load initial page
        webView.setWebViewClient(new WebViewClient());
        webView.loadUrl("https://www.google.com");

        // Handle "GO" key in soft keyboard
        urlInput.setOnEditorActionListener((v, actionId, event) -> {
            if (actionId == EditorInfo.IME_ACTION_GO || actionId == EditorInfo.IME_ACTION_DONE) {
                loadUrlFromInput();
                return true;
            }
            return false;
        });

        // Navigation buttons
        backButton.setOnClickListener(v -> {
            if (webView.canGoBack()) webView.goBack();
        });

        forwardButton.setOnClickListener(v -> {
            if (webView.canGoForward()) webView.goForward();
        });
    }

    private void loadUrlFromInput() {
        String url = urlInput.getText().toString().trim();
        if (!url.startsWith("http")) {
            url = "https://" + url;
        }
        webView.loadUrl(url);
    }

    // Handle back press using OnBackPressedDispatcher
    private final OnBackPressedCallback backPressedCallback = new OnBackPressedCallback(true) {
        @Override
        public void handleOnBackPressed() {
            if (webView.canGoBack()) {
                webView.goBack();
            } else {
                finish();
            }
        }
    };
}

🧱 activity_main.xml – Layout Code

<?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="match_parent"
    android:orientation="vertical">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:padding="8dp">

        <ImageButton
            android:id="@+id/backButton"
            android:layout_width="40dp"
            android:layout_height="40dp"
            android:background="@android:color/transparent"
            android:contentDescription="Back"
            android:src="@android:drawable/ic_media_previous" />

        <EditText
            android:id="@+id/urlInput"
            android:layout_width="0dp"
            android:layout_height="40dp"
            android:layout_weight="1"
            android:background="@drawable/et_bg"
            android:hint="Enter URL"
            android:imeOptions="actionGo"
            android:inputType="textUri"
            android:lines="1"
            android:layout_marginStart="3dp"
            android:layout_marginEnd="3dp"
            android:paddingStart="8dp"
            android:paddingEnd="8dp" />

        <ImageButton
            android:id="@+id/forwardButton"
            android:layout_width="40dp"
            android:layout_height="40dp"
            android:background="@android:color/transparent"
            android:contentDescription="Forward"
            android:src="@android:drawable/ic_media_next" />

    </LinearLayout>

    <ProgressBar
        android:id="@+id/progressBar"
        style="?android:attr/progressBarStyleHorizontal"
        android:layout_width="match_parent"
        android:layout_height="4dp"
        android:indeterminate="false"
        android:max="100" />

    <WebView
        android:id="@+id/webView"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1" />

</LinearLayout>



Download Source Code

🎨 UI Design Tips

  • Use a clean layout with a URL input bar on top.
  • Keep navigation buttons intuitive with back/forward arrows.
  • Use a small progress bar under the input to show load progress.