r/androiddev

▲ 1 r/androiddev+1 crossposts

My app won't open from tapping the notification?

I've been trying to get it to work for about an hour, no amount of AI is helping me and no errors are displayed. I am using the official android development program to make an app and after getting the notifications working, I cannot get it to open the app from the notification, what could be the problem?

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.wordoftheday">

    <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="Word of the Day"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.AppCompat.DayNight.NoActionBar">

        <activity
            android:name=".MainActivity"
            android:exported="true"
            android:launchMode="singleTop"> <!-- Add this attribute here -->
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <!-- Receiver definition -->
        <receiver
            android:name=".DailyNotificationReceiver"
            android:enabled="true"
            android:exported="false" />

    </application>

</manifest>
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.wordoftheday">

    <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="Word of the Day"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.AppCompat.DayNight.NoActionBar">

        <activity
            android:name=".MainActivity"
            android:exported="true"
            android:launchMode="singleTop"> <!-- Add this attribute here -->
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <!-- Receiver definition -->
        <receiver
            android:name=".DailyNotificationReceiver"
            android:enabled="true"
            android:exported="false" />

    </application>

</manifest>

DailyNotificationReceiver.kt

package com.example.wordoftheday

import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.os.Build
import androidx.core.app.NotificationCompat
import androidx.core.app.TaskStackBuilder
import java.io.BufferedReader
import java.io.InputStreamReader
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale

class DailyNotificationReceiver : BroadcastReceiver() {

    override fun onReceive(context: Context, intent: Intent) {
        val sharedPref = context.getSharedPreferences("WebViewSettings", Context.MODE_PRIVATE)
        val currentLevel = sharedPref.getString("word_cefr_level", "A1") ?: "A1"

        val (headword, definition) = getWordForToday(context, currentLevel)
        showNotification(context, "$currentLevel Word of the Day:\n$headword", definition)
    }

    private fun getWordForToday(context: Context, level: String): Pair<String, String> {
        try {
            val prefs = context.getSharedPreferences("WordAppPrefs", Context.MODE_PRIVATE)
            val todayStr = SimpleDateFormat("yyyy-MM-dd", Locale.US).format(Date())
            var firstDateStr = prefs.getString("word_app_first_date", null)

            if (firstDateStr == null) {
                firstDateStr = todayStr
                prefs.edit().putString("word_app_first_date", firstDateStr).apply()
            }

            val format = SimpleDateFormat("yyyy-MM-dd", Locale.US)
            val firstDate = format.parse(firstDateStr) ?: Date()
            val currentDate = format.parse(todayStr) ?: Date()

            // Clean, fixed date math calculation without any stray characters
            val diffDays = ((currentDate.time - firstDate.time) / (1000 * 60 * 60 * 24)).toInt()
            val dayNumber = Math.max(1, diffDays + 1)

            val inputStream = context.assets.open("$level.csv")
            val reader = BufferedReader(InputStreamReader(inputStream))
            val lines = reader.readLines().drop(1).filter { it.isNotBlank() }

            if (lines.isEmpty()) return Pair("Vocabulary", "Check today's new word in the app!")

            val wordIndex = (dayNumber - 1) % lines.size
            val parsedRow = parseCSVLine(lines[wordIndex])

            if (parsedRow.size >= 4) {
                return Pair(parsedRow[1], parsedRow[3])
            }
        } catch (e: Exception) {
            e.printStackTrace()
        }
        return Pair("Word of the Day", "Tap to learn your new word for today!")
    }

    private fun parseCSVLine(line: String): List<String> {
        val row = mutableListOf<String>()
        var inQuotes = false
        var currentVal = StringBuilder()

        for (i in line.indices) {
            val char = line[i]
            if (char == '"') {
                inQuotes = !inQuotes
            } else if (char == ',' && !inQuotes) {
                row.add(currentVal.toString().trim().removeSurrounding("\""))
                currentVal = StringBuilder()
            } else {
                currentVal.append(char)
            }
        }
        row.add(currentVal.toString().trim().removeSurrounding("\""))
        return row
    }

    private fun showNotification(context: Context, title: String, message: String) {
        val channelId = "daily_word_channel"
        val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val channel = NotificationChannel(
                channelId,
                "Daily Word Reminders",
                NotificationManager.IMPORTANCE_DEFAULT
            )
            notificationManager.createNotificationChannel(channel)
        }

        // 1. Explicitly point to MainActivity and apply clear/single flags
        val resultIntent = Intent(context, MainActivity::class.java).apply {
            flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP
        }

        // 2. Generate a valid, safe PendingIntent directly from the intent
        val pendingIntent = PendingIntent.getActivity(
            context,
            1001, // Use a distinct request code instead of 0
            resultIntent,
            PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
        )

        // 3. Build and fire the notification
        val builder = NotificationCompat.Builder(context, channelId)
            .setSmallIcon(android.R.drawable.ic_popup_reminder)
            .setContentTitle(title)
            .setContentText(message)
            .setStyle(NotificationCompat.BigTextStyle().bigText(message))
            .setPriority(NotificationCompat.PRIORITY_DEFAULT)
            .setContentIntent(pendingIntent) // Passes the valid intent
            .setAutoCancel(true)

        notificationManager.notify(1001, builder.build())
    }

}package com.example.wordoftheday

import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.os.Build
import androidx.core.app.NotificationCompat
import androidx.core.app.TaskStackBuilder
import java.io.BufferedReader
import java.io.InputStreamReader
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale

class DailyNotificationReceiver : BroadcastReceiver() {

    override fun onReceive(context: Context, intent: Intent) {
        val sharedPref = context.getSharedPreferences("WebViewSettings", Context.MODE_PRIVATE)
        val currentLevel = sharedPref.getString("word_cefr_level", "A1") ?: "A1"

        val (headword, definition) = getWordForToday(context, currentLevel)
        showNotification(context, "$currentLevel Word of the Day:\n$headword", definition)
    }

    private fun getWordForToday(context: Context, level: String): Pair<String, String> {
        try {
            val prefs = context.getSharedPreferences("WordAppPrefs", Context.MODE_PRIVATE)
            val todayStr = SimpleDateFormat("yyyy-MM-dd", Locale.US).format(Date())
            var firstDateStr = prefs.getString("word_app_first_date", null)

            if (firstDateStr == null) {
                firstDateStr = todayStr
                prefs.edit().putString("word_app_first_date", firstDateStr).apply()
            }

            val format = SimpleDateFormat("yyyy-MM-dd", Locale.US)
            val firstDate = format.parse(firstDateStr) ?: Date()
            val currentDate = format.parse(todayStr) ?: Date()

            // Clean, fixed date math calculation without any stray characters
            val diffDays = ((currentDate.time - firstDate.time) / (1000 * 60 * 60 * 24)).toInt()
            val dayNumber = Math.max(1, diffDays + 1)

            val inputStream = context.assets.open("$level.csv")
            val reader = BufferedReader(InputStreamReader(inputStream))
            val lines = reader.readLines().drop(1).filter { it.isNotBlank() }

            if (lines.isEmpty()) return Pair("Vocabulary", "Check today's new word in the app!")

            val wordIndex = (dayNumber - 1) % lines.size
            val parsedRow = parseCSVLine(lines[wordIndex])

            if (parsedRow.size >= 4) {
                return Pair(parsedRow[1], parsedRow[3])
            }
        } catch (e: Exception) {
            e.printStackTrace()
        }
        return Pair("Word of the Day", "Tap to learn your new word for today!")
    }

    private fun parseCSVLine(line: String): List<String> {
        val row = mutableListOf<String>()
        var inQuotes = false
        var currentVal = StringBuilder()

        for (i in line.indices) {
            val char = line[i]
            if (char == '"') {
                inQuotes = !inQuotes
            } else if (char == ',' && !inQuotes) {
                row.add(currentVal.toString().trim().removeSurrounding("\""))
                currentVal = StringBuilder()
            } else {
                currentVal.append(char)
            }
        }
        row.add(currentVal.toString().trim().removeSurrounding("\""))
        return row
    }

    private fun showNotification(context: Context, title: String, message: String) {
        val channelId = "daily_word_channel"
        val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val channel = NotificationChannel(
                channelId,
                "Daily Word Reminders",
                NotificationManager.IMPORTANCE_DEFAULT
            )
            notificationManager.createNotificationChannel(channel)
        }

        // 1. Explicitly point to MainActivity and apply clear/single flags
        val resultIntent = Intent(context, MainActivity::class.java).apply {
            flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP
        }

        // 2. Generate a valid, safe PendingIntent directly from the intent
        val pendingIntent = PendingIntent.getActivity(
            context,
            1001, // Use a distinct request code instead of 0
            resultIntent,
            PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
        )

        // 3. Build and fire the notification
        val builder = NotificationCompat.Builder(context, channelId)
            .setSmallIcon(android.R.drawable.ic_popup_reminder)
            .setContentTitle(title)
            .setContentText(message)
            .setStyle(NotificationCompat.BigTextStyle().bigText(message))
            .setPriority(NotificationCompat.PRIORITY_DEFAULT)
            .setContentIntent(pendingIntent) // Passes the valid intent
            .setAutoCancel(true)

        notificationManager.notify(1001, builder.build())
    }
reddit.com
u/Adamstrad — 3 days ago
▲ 38 r/androiddev+1 crossposts

I made a customizable squircle bottom navigation bar for Android (SqNav)

Hey everyone,

I recently built an open-source Android library called SqNav. It's a bottom navigation bar designed with floating squircle card transitions and a glowing indicator for the active tab.

I wanted something clean, with smooth spring animations, and easy to customize without building custom drawables and animations from scratch every time.

A few highlights:

  • Floating squircle card with spring overshoot animations for the active tab
  • Neon glow indicator dot on top of the selected item (can be toggled on or off)
  • Full styling control through XML or code (background, corner radius, stroke borders, sizes, and colors)
  • Compatible with Jetpack Compose, Kotlin Views, and Java
  • Lightweight with minimal overhead

Attached is a quick preview GIF showing it in action.

You can check out the implementation, sample code, and documentation on GitHub: sqnav

I would really appreciate any feedback, suggestions, or thoughts on the design. If you run into any issues or want to contribute, feel free to open an issue or PR!

u/Ahmed_El_Gendy — 5 days ago
▲ 2 r/androiddev+1 crossposts

Is Kotlin Multiplatform a good long-term career bet for Android developers in India?

Hi everyone!

I'd like to get your thoughts on the long-term career prospects of Android development, particularly Kotlin Multiplatform (KMP) and Compose Multiplatform (CMP).

With cross-platform technologies gaining traction, I'm curious about how Android developers should evaluate KMP/CMP compared with focusing on native Android development or moving toward backend and full-stack engineering.

For developers working with KMP/CMP in production:

  • Are you seeing meaningful adoption in companies ?
  • Does KMP experience open up more opportunities, or are most roles still focused on native Android?
  • How transferable are these skills to broader software engineering roles?
  • If you were planning your career for the next 3–5 years, would you prioritise native Android, KMP/CMP, or a broader full-stack skill set?

I'd appreciate perspectives from people working with these technologies in real-world projects.

reddit.com
u/OpeningArm7095 — 5 days ago

What’s your go-to cross-platform TTS (Android/iOS/Windows) right now? Tired of hacky workarounds.

Hey fellow Android devs,

I'm hitting a wall with Text-to-Speech integration on the native side.

I was using a 3rd party C++ library for my TTS, but the final straw was dealing with Google Play's new 16KB page size alignment requirement for native binaries. The precompiled .so libraries obviously weren't 16K compatible, and having to jump through hoops to patch binaries just to get the build accepted by the console is exhausting. Dealing with unmaintained native plugins is a nightmare right now.

I need a solid, actively maintained offline TTS solution that plays nice via NDK/C++ without requiring me to fight the compiler every time Google updates its requirements.

How are you guys handling offline C++ TTS these days when you can't just rely purely on the standard Java/Kotlin Android API? Any hidden gems?

Thanks in advance for saving my sanity!

reddit.com
u/Alternative_Hand4512 — 6 days ago
▲ 3 r/androiddev+1 crossposts

Google Play wants organization verification for my payments profile, but I'm an individual

Hi everyone,

I've had a Google Play developer account for years. It's an individual account, and the identity payments profile under "About you" is also individual.

When I set up the merchant payments profile used for payouts, I picked "Organization" by mistake, even though every detail on it is my own personal name and address. I've never had a registered company or a VAT number.

In the past Google asked me to verify this same merchant profile and accepted my government ID. Now a new verification request asks for a VAT attribution certificate or a company registration document, which I simply don't have because no company exists.

According to the help pages, a payments profile can't be switched from Organization to Individual, and the merchant profile can't be unlinked from the developer account. So I'm stuck: there's a significant balance pending on the profile, and the account has apps with active users and subscriptions that I really don't want to lose.

I contacted support and opened a ticket more than a week ago, but I still haven't received any reply.

Has anyone been through this? Is there a way to get support to fix the profile type, or to verify it as an individual like before?

Thanks.

reddit.com
u/Special-Contest6810 — 8 days ago

Google Play App Review times?

Hi Folks, its been over a week now since I submitted my App for review with Google. Is there a reasonable timeframe that one should wait before maybe submitting a ticket for a prompt to review at all. What do you guys do in such instances? Many thanks!

reddit.com
u/SirMaxxi — 14 days ago

App in review for 15 days

Wondering if anyone experienced super long delay in app review recently, my app was submitted for review on 9/17 and have been 'in review' since then, going strong on 15 days now.

My dev account is like 10+ years old, but I haven't published anything in a few years. Have Google Play publishing changed to be this slow?

Are there tricks people follow to get their apps reviewed fast?

reddit.com
u/dvdhh — 14 days ago