App stürzt ab

KarlNapp

Mitglied
Hallo in die Runde,

ich bin neu in Kotlin und möchte eine App programmieren, in der alle 15 Min eine Glocke schlägt, wie bei einer Kirchturmuhr. Das klappt auch insoweit bis zu dem Punkt, wo ich die nächste "Weckzeit" stellen will (Befehl "instance.zeitStellen()" im Alarm Receiver). Dann stürzt die App ab mit einer "NullPointerException". Kann mir jemand helfen?

MainActivity:

Java:
package com.example.alarmtest01

import android.app.AlarmManager
import android.app.PendingIntent
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.media.MediaPlayer
import android.os.Bundle
import android.widget.Toast
import androidx.activity.ComponentActivity
import java.util.Calendar

class MainActivity : ComponentActivity() {

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

        setContentView(R.layout.activity_main)

        zeitStellen()
    }
    
        public fun zeitStellen() {
            val intent = Intent(this, AlarmReceiver::class.java)
            val pendingIntent = PendingIntent.getBroadcast(
                this, 0,
                intent, PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_MUTABLE
            )
            val alarmManager = getSystemService(ALARM_SERVICE) as AlarmManager

            val zeit = Calendar.getInstance()
            var minute = zeit.get(Calendar.MINUTE)
            var stunde = zeit.get(Calendar.HOUR)
            if (minute < 15) minute = 15
            else if (minute < 30) minute = 40
            else if (minute < 45) minute = 45
            else {
                minute = 0
                stunde++
            }
            zeit.set(
                zeit.get(Calendar.YEAR),
                zeit.get(Calendar.MONTH),
                zeit.get(Calendar.DAY_OF_MONTH),
                stunde,
                minute
            )
            alarmManager[AlarmManager.RTC_WAKEUP, zeit.timeInMillis] = pendingIntent
            Toast.makeText(this, "Alarm set", Toast.LENGTH_LONG).show()
        }

}

und AlarmReceiver:

Code:
package com.example.alarmtest01

import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.media.MediaPlayer
import android.widget.Toast

class AlarmReceiver : BroadcastReceiver() {
    var mMediaPlayer: MediaPlayer? = null
    
    val instance = MainActivity()

    override fun onReceive(context: Context, intent: Intent) {
        Toast.makeText(context, "Alarm worked.", Toast.LENGTH_LONG).show()

        fun playSoundHell() {
            if (mMediaPlayer == null) {
                mMediaPlayer = MediaPlayer.create(context, R.raw.alte_kirche_einzelschlag_hell)
                mMediaPlayer!!.start()
            } else mMediaPlayer!!.start()
        }
        fun playSoundHellEnde() {
            if (mMediaPlayer == null) {
                mMediaPlayer = MediaPlayer.create(context, R.raw.alte_kirche_einzelschlag_hell_ende)
                mMediaPlayer!!.start()
            } else mMediaPlayer!!.start()
        }

        fun bimmelGlockeHell(anzahl: Int) {
            if (anzahl > 0) {
                for (i in 1..anzahl) {
                    println(i)
                    playSoundHell()
                    while (mMediaPlayer!!.isPlaying) {
                        // warten
                    }
                }
                mMediaPlayer!!.release()
                mMediaPlayer = null
            }
            playSoundHellEnde()
            while (mMediaPlayer!!.isPlaying) {
                // warten
            }
            mMediaPlayer!!.release()
            mMediaPlayer = null
        }

        //AsyncTask.execute {
        bimmelGlockeHell(3)
        //}
        
        instance.zeitStellen()
    }
}

Besten Dank und Grüße
KarlNapp
 

KonradN

Super-Moderator
Mitarbeiter
Bei einer Exception solltest Du auch einen Stacktrace bekommen haben. Kannst Du auch den genauen Stacktrace mitteilen? Und angeben, welche Zeilen da genau genannt werden. (Falls die Dateien nicht komplett 1:1 kopiert sind!)
 

KarlNapp

Mitglied
Hallo KonradN,

danke für Deine Antwort. Ist es das, was Du meinst:

Code:
FATAL EXCEPTION: main
Process: com.example.alarmtest01, PID: 24840
java.lang.RuntimeException: Unable to start receiver com.example.alarmtest01.AlarmReceiver: java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.String android.content.Context.getPackageName()' on a null object reference
    at android.app.ActivityThread.handleReceiver(ActivityThread.java:4458)
    at android.app.ActivityThread.-$$Nest$mhandleReceiver(Unknown Source:0)
    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2256)
    at android.os.Handler.dispatchMessage(Handler.java:106)
    at android.os.Looper.loopOnce(Looper.java:205)
    at android.os.Looper.loop(Looper.java:294)
    at android.app.ActivityThread.main(ActivityThread.java:8177)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:552)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:971)
Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.String android.content.Context.getPackageName()' on a null object reference
    at android.content.ContextWrapper.getPackageName(ContextWrapper.java:177)
    at android.content.ComponentName.<init>(ComponentName.java:132)
    at android.content.Intent.<init>(Intent.java:7650)
    at com.example.alarmtest01.MainActivity.zeitStellen(MainActivity.kt:73)
    at com.example.alarmtest01.AlarmReceiver.onReceive(AlarmReceiver.kt:74)
    at android.app.ActivityThread.handleReceiver(ActivityThread.java:4449)
    at android.app.ActivityThread.-$$Nest$mhandleReceiver(Unknown Source:0) 
    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2256) 
    at android.os.Handler.dispatchMessage(Handler.java:106) 
    at android.os.Looper.loopOnce(Looper.java:205) 
    at android.os.Looper.loop(Looper.java:294) 
    at android.app.ActivityThread.main(ActivityThread.java:8177) 
    at java.lang.reflect.Method.invoke(Native Method) 
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:552) 
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:971)

Viele Grüße
KN
 

KonradN

Super-Moderator
Mitarbeiter
Also erst einmal der Hinweis: Es wäre super gewesen, wenn Du uns noch gesagt hättest, dass die Zeile 74 aus AlarmReceiver.kt das instance.zeitStellen() gewesen wäre. Und was die Zeile 73 aus MainActivity.kt wäre. Das habe ich jetzt nicht versucht zu raten...

Ich bin nicht wirklich fit in Kotlin, aber die Zeile:
val instance = MainActivity()
dürfte doch eine neue Instanz von MainActivity erzeugen. Aber Du willst doch keine neue Instanz der Activity sondern du willst die aktuelle Activity, die erzeugt wurde, als die App gestartet wurde. Die wird ja ganz anders initialisiert und so.

Ich weiss jetzt nicht, woher die Instanz von AlarmReceiver kommt, aber das wird doch vermutlich auch in der MainActivity erfolgen, oder? Da könntest Du dann die Instanz der MainActivity übergeben.

Oder (aus meiner Sicht mehr unsauber): Du machst etwas wie ein Companion Objekt und da speicherst Du dann die MainActivity:

Code:
class MainActivity : ComponentActivity() {
    
    companion object {
        var mainActivity: MainActivity? = null
    }
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        mainActivity = this;
        
        setContentView(R.layout.activity_main)

        zeitStellen()
    }

Aber diese statischen Dinge limitieren das alles auch meist, daher bin ich da eher kein Freund von.
 

Jw456

Top Contributor
Ohne mir deine Code im deteil angesehen zu haben.
Fällt mir auf das du i. Der Methode onResive neu Methoden erstellt, also Methode in Methode was nie gut ist.
 

KarlNapp

Mitglied
Sorry, dass die Zeile 73 hier nicht vorhanden ist, liegt daran, dass ich für den post wg. der Übersichtlichkeit einige Kommentarzeilen entfernt hatte. Wo der Absturz stattfindet, hatte ich allerdings beschrieben (Zeile 54) im AlarmReceiver.
Ich habe das Ganze schon einmal mit einem Microcontroler realisiert, da braucht man aber noch zusätzliche Hardware wie RTC, Verstärker und LSP, DCF77 Empfänger etc. Und da dachte ich mir, das gibt's ja alles schon in einem Handy, also probierst Du es mal dort, quasi unter Rubrik "Jugend forscht" (ich bin über 60). Und jetzt bin ich halt erstmalig mit OOP und dem ganzen Android Framework, das sich ja auch ständig ändert, konfrontiert und fühle mich davon ziemlich erschlagen, so dass ich auf Hilfe hier im Forum hoffe.
Der Code ist deshalb auch nicht komplett auf meinem Mist gewachsen: Die Zeilen 25-30 (MainAct.) und 14 (AlarmRec.) habe ich aus einem Tutorial zum AlarmManager.
Die einzige Verbindung zum AlarmReceiver findet sich in Zeile 25 in der MainActivity. Ich vermute die Problematik hängt mit dem Context zusammen, doch ist mir nicht klar, wie das korrekt zu codieren ist.

@Jw456
Obwohl das Gebimmel so funktioniert, wie macht man das richtig? Die Methoden in den Klassenrumpf schreiben?

Viele Grüße
KN
 

Jw456

Top Contributor
Dise Zeile
alarmManager[AlarmManager.RTC_WAKEUP, zeit.timeInMillis] = pendingIntent
Also den alarmmanager als Array zubenutzen ist fur noch zimindestens unüblich würde ich sicher auch nicht machen. Von wo hast du das her?
Ich würde das in etwa so machen.

Java:
val alarmManager = getSystemService(Context.ALARM_SERVICE) as AlarmManager      
val intent = Intent(this, MyAlarm::class.java)       
val pendingIntent = PendingIntent.getBroadcast(this, 0, intent, 0)      
 alarmManager.setRepeating(          AlarmManager.RTC,          timeInMillis,          AlarmManager.INTERVAL_DAY,          pendingIntent       )

Benutze die Methode setRepeating
 
Zuletzt bearbeitet:

Jw456

Top Contributor
Im Receiver bekommst du doch auch den Context der Activity mit. Damit kannst du deine zeitStellen Methode der Activity aufrufen. Um einen neuen Alam und intent zu starten.

onReceive(context: Context, intent: Intent)
context.zeitStellen()
 

KonradN

Super-Moderator
Mitarbeiter
Sorry, dass die Zeile 73 hier nicht vorhanden ist, liegt daran, dass ich für den post wg. der Übersichtlichkeit einige Kommentarzeilen entfernt hatte. Wo der Absturz stattfindet, hatte ich allerdings beschrieben (Zeile 54) im AlarmReceiver.
Die Zeile besagt aber doch nichts! Da rufst Du eine andere Methode von Dir auf und in der geht es dann weiter. In Deinem StackTrace sind ja:
at android.content.Intent.<init>(Intent.java:7650)
at com.example.alarmtest01.MainActivity.zeitStellen(MainActivity.kt:73)
at com.example.alarmtest01.AlarmReceiver.onReceive(AlarmReceiver.kt:74)

Also Dein onReceive ruft zeitStellen auf und da geht es dann in dem Konstruktor von Intent weiter. Und meine Vermutung ist nun einmal, dass das einfach daran liegt, dass die Activity nicht richtig initialisiert ist...

Einfaches Beispiel:
Du bekommst einen BMW von der Firma gestellt. Da sind ganz viele Werkzeuge drin, die Du für deinen Job brauchst.
Und wenn Du dann irgendwo dein Werkzeug brauchst, dann gehst Du hin und holst Dir einen neuen BMW und wunderst Dich dann, dass da die Werkzeuge nicht drin sind. Ist doch auch klar: Der BMW der Firma wurde ja von der Firma aufbereitet und beladen. Der neue BMW ist einfach ein neuer BMW und der hat diese Initialisierungen nicht.

Daher auch die Ideen von mir, wie Du die richtige Instanz von MainActivity bekommen kannst.

Im Receiver bekommst du doch auch den Context der Activity mit. Damit kannst du deine zeitStellen Methode der Activity aufrufen. Um einen neuen Alam und intent zu starten.

onReceive(context: Context, intent: Intent)
context.zeitStellen()
Wenn der Context die MainActivity ist, dann müsste da mindestens noch ein Cast her. Also eine Prüfung + Aufruf.
 

Jw456

Top Contributor
Die Zeile besagt aber doch nichts! Da rufst Du eine andere Methode von Dir auf und in der geht es dann weiter. In Deinem StackTrace sind ja:
at android.content.Intent.<init>(Intent.java:7650)
at com.example.alarmtest01.MainActivity.zeitStellen(MainActivity.kt:73)
at com.example.alarmtest01.AlarmReceiver.onReceive(AlarmReceiver.kt:74)

Also Dein onReceive ruft zeitStellen auf und da geht es dann in dem Konstruktor von Intent weiter. Und meine Vermutung ist nun einmal, dass das einfach daran liegt, dass die Activity nicht richtig initialisiert ist...

Einfaches Beispiel:
Du bekommst einen BMW von der Firma gestellt. Da sind ganz viele Werkzeuge drin, die Du für deinen Job brauchst.
Und wenn Du dann irgendwo dein Werkzeug brauchst, dann gehst Du hin und holst Dir einen neuen BMW und wunderst Dich dann, dass da die Werkzeuge nicht drin sind. Ist doch auch klar: Der BMW der Firma wurde ja von der Firma aufbereitet und beladen. Der neue BMW ist einfach ein neuer BMW und der hat diese Initialisierungen nicht.

Daher auch die Ideen von mir, wie Du die richtige Instanz von MainActivity bekommen kannst.


Wenn der Context die MainActivity ist, dann müsste da mindestens noch ein Cast her. Also eine Prüfung + Aufruf.
Er startet den AM in der Activity somit ist es der Context der Activity.
In Java habe ich das immer so gemacht.
Cast ja kann sein.
 

KarlNapp

Mitglied
Danke für Eure Antworten.

@Jw456
context.zeitStellen()
funktioniert nicht. Ist das der richtige Befehl oder nur ein Hinweis darauf, wie es gehen könnte? Da fehlt mir leider der Hintergrund.
Den Aufruf für den AlarmManager habe ich umgeschrieben so wie in Deinem Vorschlag, gefällt mir auch besser.

@KonradN
Was meinst Du damit, dass die Activity nicht richtig initialisiert ist? Wo kann ich das kontrollieren oder beheben?

Schönes WE
KN
 

Jw456

Top Contributor
Die activuty ist ordentlich erstellt.
Nur dein Zugriff darauf ist falsch. Denn wie er dir an dem bmw Beispiel erklärte ist das was du zum aufrufst benutzt nicht die gleiche instanz davon.
Auch wenn der Compiler keinen Fehler beim Build gibt.
 

philanthrop

Bekanntes Mitglied
"Theoretisch" sollte folgendes funktionieren (mit Android "S" 12.0+):

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

    <TextView
        android:id="@+id/tv1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

(Wichtig ist hier die ID tv1)

Java:
package com.example.mynotify;

import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.widget.TextView;

import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.NotificationCompat;

import java.util.Locale;
import java.util.Timer;
import java.util.TimerTask;

public class MainActivity extends AppCompatActivity {
    private Timer timer;
    private long stopAt;
    private final long amount = 1000 * 16 * 15;
    private final TimerTask runnable = new TimerTask() {
        @Override
        public void run() {
            long t = System.currentTimeMillis();
            if (t < stopAt) {
                long duration = stopAt - t;
                TextView tv1 = findViewById(R.id.tv1);
                tv1.setText(String.format(Locale.GERMAN, "%02d:%02d:%03d", duration / (1000 * 60), (duration / 1000) % 60, duration % 1000));
            } else {
                stopAt += amount;
                myNotify();
            }
        }
    };

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

    @Override
    protected void onStart() {
        super.onStart();
    }

    @Override
    protected void onResume() {
        super.onResume();
        if (timer == null) {
            timer = new Timer();
            stopAt = System.currentTimeMillis() + amount;
            timer.scheduleAtFixedRate(runnable, 1000, 100);
        }
    }

    @Override
    protected void onPause() {
        super.onPause();
        if (timer != null) {
            timer.cancel();
            timer = null;
        }
    }

    private void myNotify() {
        Context context = getApplicationContext();

        NotificationManager manager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
        manager.createNotificationChannel(new NotificationChannel("AaAa", "MyNotify", NotificationManager.IMPORTANCE_DEFAULT));

        Intent intent = new Intent(context, MainActivity.class);
        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
        NotificationCompat.Builder builder = new NotificationCompat.Builder(context, "AaAa");
        builder
                .setSmallIcon(R.mipmap.ic_launcher)
                .setContentTitle("MyTitle")
                .setContentText("Countdown down!!!")
                .setContentIntent(pendingIntent)
                .setAutoCancel(true);
        manager.notify(0, builder.build());

        //intent.AddFlags(ActivityFlags.ClearTop);
    }
}

(Wichtig ist hier die 100-ms-Period)

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

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

    <application
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.MyNotify"
        tools:targetApi="31">
        <activity
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

(Wichtig ist hier die Permission: POST_NOTIFICATIONS, die auch manuell vorher in den Einstellungen gewährt werden muss)

1695413676518.png

Praktisch jedoch nicht ... Mit Android 13.0 wird die App entweder sofort beendet oder aber die UI aktualisiert sich nicht. Ich weiß net, weshalb.
 

philanthrop

Bekanntes Mitglied
Hab den Fehler dank https://stackoverflow.com/questions...exception-task-already-scheduled-or-cancelled gefunden: Man sollte einen ScheduledExecutorService und runOnUiThread verwenden:

1695460294155.png

Java:
package com.example.mynotify;

import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.content.Context;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.os.Build;
import android.os.Bundle;
import android.widget.TextView;

import androidx.annotation.RequiresApi;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.NotificationCompat;

import java.util.Locale;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class MainActivity extends AppCompatActivity {
    private ScheduledExecutorService scheduledExecutorService;
    private long stopAt;
    // sends every 2 minutes a notification
    private final long amount = 1000 * 60 * 2;

    @RequiresApi(api = Build.VERSION_CODES.TIRAMISU)
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        askForPermission();
    }

    @RequiresApi(api = Build.VERSION_CODES.TIRAMISU)
    private void askForPermission() {
        //if (ActivityCompat.checkSelfPermission(this, android.Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) {
        requestPermissions(new String[]{android.Manifest.permission.POST_NOTIFICATIONS}, 1);
        //}
    }

    @Override
    protected void onStart() {
        super.onStart();
    }

    @Override
    protected void onResume() {
        super.onResume();
        if (scheduledExecutorService == null) {
            scheduledExecutorService = Executors.newScheduledThreadPool(1);
            stopAt = System.currentTimeMillis() + amount;
            scheduledExecutorService.scheduleAtFixedRate(() -> {
                long t = System.currentTimeMillis();
                if (t < stopAt) {
                    long duration = stopAt - t;
                    runOnUiThread(() -> {
                        TextView tv1 = findViewById(R.id.tv1);
                        tv1.setText(String.format(Locale.GERMAN, "%02d:%02d:%03d", duration / (1000 * 60), (duration / 1000) % 60, duration % 1000));
                    });
                } else {
                    stopAt += amount;
                    showNotification();
                }
            }, 1000, 100, TimeUnit.MILLISECONDS);
        }
    }

    @Override
    protected void onPause() {
        super.onPause();
        if (scheduledExecutorService != null) {
            scheduledExecutorService.shutdown();
            scheduledExecutorService = null;
        }
    }

    private void showNotification() {
        String channelId = "15342";
        String description = "Test Notification";

        NotificationManager notificationManager =
                (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);

        NotificationChannel notificationChannel =
                new NotificationChannel(channelId, description, NotificationManager.IMPORTANCE_HIGH);
        notificationChannel.setLightColor(Color.BLUE);

        notificationChannel.enableVibration(true);
        notificationManager.createNotificationChannel(notificationChannel);

        NotificationCompat.Builder builder = new NotificationCompat.Builder(this, channelId)
                .setContentTitle("Hello World")
                .setContentText("Test Notification")
                .setSmallIcon(R.drawable.ic_launcher_foreground)
                .setLargeIcon(
                        BitmapFactory.decodeResource(
                                this.getResources(), R.drawable
                                        .ic_launcher_background
                        )
                );
        notificationManager.notify(15342, builder.build());
    }
}

Was hat das jetzt mit dem Thema des Threads vom TE zu tun?
Weil er danach gefragt hatte, alle 15 Minuten eine Benachrichtigung zu senden.
 

KarlNapp

Mitglied
@Jw456
Die activuty ist ordentlich erstellt.
Nur dein Zugriff darauf ist falsch. Denn wie er dir an dem bmw Beispiel erklärte ist das was du zum aufrufst benutzt nicht die gleiche instanz davon.
Auch wenn der Compiler keinen Fehler beim Build gibt.
Das Beispiel mit dem BMW glaubte ich verstanden zu haben und habe keine neue Instanz erstellt, sondern direkt

MainActivity().zeitStellen()

benutzt. Das führt aber leider ebenfalls zum Absturz. Bin ich da immer noch auf dem falschen Dampfer?


@philanthrop
Vielen Dank für Dein Beispiel. Ist halt leider kein Kotlin. Und wenn ich es richtig sehe, pausiert die App bei abgeschaltetem Bildschirm. Das gerade wollte ich vermeiden, das Handy soll ja alle 15 Min. bimmeln solange die App läuft, egal in welchem Zustand.
Ich hatte ja schomal eine Version, allerdings ohne AlarmManger, sondern mit einem Chronometer mit onTickListener quasi als Taktgeber für den Zeitvergleich. Funktioniert prima, solange der Bldschirm nicht ausgeht.

Viele Grüße
KarlNapp
 

KonradN

Super-Moderator
Mitarbeiter
Das Beispiel mit dem BMW glaubte ich verstanden zu haben und habe keine neue Instanz erstellt, sondern direkt

MainActivity().zeitStellen()

benutzt. Das führt aber leider ebenfalls zum Absturz. Bin ich da immer noch auf dem falschen Dampfer?
Ja, Grundlagen der Kotlin Sprache ... MainActivity() erzeugt doch eine neue Instanz. Egal, ob Du diese nun erst in einer Variablen speicherst und dann nutzt oder diese direkt nutzt ist doch egal. Sprich: Ob Du den neu gekauften BMW erst in einer Garage parkst oder direkt versuchst, da etwas aus dem Kofferaum, zu nehmen wird keinen Unterschied machen.

Es sind jetzt 2 LösungsIdeen im Thread, die Du umsetzen kannst:

1. in #4 habe ich gezeigt, wie Du in MainActivity eine statische Variable einführen kannst und Du diese dann mit der Referenz zu der erzeugten MainActivity füllst. Diese Instanz kannst Du dann nutzen, indem du z.B. MainActivity.mainActivity.zeitStellen() aufrufst.

2. Der Hinweis mit dem Context Parameter. Du kannst prüfen, ob der Parameter vom Typ MainActivity ist und dann zeitStellen aufrufen. Da wäre der Aufruf einfach ein
if (context is MainActivity) context.zeitStellen()

Empfehlung: Du solltest Dir diese grundlegenden Elemente der Kotlin Sprache noch einmal im Detail ansehen:
Classes | Kotlin Documentation (kotlinlang.org)
Type checks and casts | Kotlin Documentation (kotlinlang.org)

An der Stelle ggf. auch noch der Hinweis, dass man hier. die Anwendung noch etwas besser strukturieren sollte. Die Problematik kommt mit daher, weil MainActivity Dinge enthält, die da nicht wirklich hinein gehören. Aber das findet man leider öfters bei Client Anwendungen (Egal ob Desktop, Smartphone, ... unabhängig von der Technologie), dass hier Dinge in UI Klassen gepackt werden, die da nicht wirklich hinein gehören, aber es ist einfach bequem, es da mit hinein zu packen. Hier hat man etwas, das eher in einen Controller oder so gehören würde als eben in die View Klasse (Die MainActivity würde ich dann eher zu einer View zuordnen. Aber das Pattern ist halt nicht wirklich vorhanden, daher ist es schwer, hier z.B. die Begriffe vom MVC Pattern zu verwenden.)
 

KarlNapp

Mitglied
Doch, das würde mir schon gelingen. Aber lehrreicher ist es doch, wenn ich erstmal bei meinem Beispiel bleibe uns es schaffe die Methode korrekt aufzurufen. Vielleicht packt es mich ja dann und ich probiere noch eine Variante mit Deinem Vorschlag.
Nix für ungut.
 

Jw456

Top Contributor
Die Geschichte über den Context zu lösen ist hier nicht die beste Wahl. Besser und einfacher ist es über das Companien Objekt. Was schon gezeigt wurde. Das kannst du mit einer static Variablen in Java vergleichen.


Wenn ohne Companion dann müsstest du die Instanz der Activity an den Receiver übergeben.




Java:
class MainActivity : ComponentActivity() {
    companion object {
        var mainActivity: MainActivity? = null
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        mainActivity = this;
        setContentView(R.layout.activity_main)
        zeitStellen()
    }
....
  
    //Receiver
     bimmelGlockeHell(3)
     MainActivity.mainActivity?.zeitStellen()
 

KonradN

Super-Moderator
Mitarbeiter
Das verursacht meiner Erfahrung nach nur Probleme und stellt ein Sicherheits breach dar ... besser das mit diesem Companion
Wo siehst Du Probleme oder ein Security Breach? Vor allem die Sicherheit ist doch deutlich schlechter bei einer public static Variable, die von überall zugegriffen werden kann.

Und das man Dinge übergibt, die benötigt werden, ist doch generell das übliche Vorgehen. Wobei hier keine Activity übergeben werden müsste. Die MainActivity spielt doch gar keine Rolle so ich die Methode richtig gesehen habe. Die Methode zeitStellen gehört nur eben nicht in MainActivity. Die Methode macht ja auch nichts mit MainActivity. Die MainActivity wird nur benötigt, wo der Context benötigt wird. Damit kann es eine einfache Methode sein, die den Context als Parameter bekommt und dann müsste man an den drei Stellen "this" durch den Parameter ersetzen und das dürfte es schon fast gewesen sein.
 

KarlNapp

Mitglied
Danke. Mit dem Companion klappt's - kein Absturz mehr. Aber es ist wie verhext, jetzt bimmelt's ständig, weil fälschlicher Weise eine "alte" Zeit gesetzt wird.
zeit.get(Calendar.MONTH) liefert nämlich 8, obwohl schon September ist. Ich hoffe, dass das nur am Emulator liegt und auf dem realen Handy ordentlich funktioniert. Kann ich aber erst später ausprobieren.

Schönen Sonntag noch
 

KonradN

Super-Moderator
Mitarbeiter
Mein Tipp bezüglich Sprache gilt natürlich auch für die Library, die Du verwendest. Du solltest Dir die Dokumentation zu den Klassen ansehen, die Du verwendest. So ist deine Fehlersuche nur ein raten / vermuten.

Calendar | Android Developers
Dort speziell einfach mal die Dokumentation zu MONTH ansehen:
Calendar | Android Developers
Field number for get and set indicating the month. This is a calendar-specific value. The first month of the year in the Gregorian and Julian calendars is JANUARY which is 0; the last depends on the number of months in a year.

Monate sind also 0 basiert und der Monat September ist daher tatsächlich der Index 8.

Wenn Du einen Fehler hast, dann wird dieser also vermutlich an anderer Stelle zu suchen sein. Evtl. willst Du einfach noch einmal Deinen genauen Code zeigen?
 

Ähnliche Java Themen

Neue Themen


Oben