r/AndroidStudio

▲ 1 r/AndroidStudio+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/AndroidStudio+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