I'm having trouble running this small Java-based Android Studio app on my Android Emulator. When I try to schedule a job using a job scheduler feature for notifications on Android, my App crashes and gives me this error message "Caused by: java.lang.IllegalArgumentException: No such service ComponentInfo{com.example.notificationscheduler/com.example.notificationscheduler.NotificationJobService}". Please help me, my app information is down below.
I'm on the latest version of Android Studio.
Build.Gradle Module:app configuration details:
compileSdkVersion 29
buildToolsVersion "29.0.2"
defaultConfig {
applicationId "com.example.notificationscheduler"
minSdkVersion 21
targetSdkVersion 29
versionCode 1
versionName "1.0"
My MainActivity Class Code:
package com.example.notificationscheduler;
import android.app.job.JobInfo;
import android.app.job.JobScheduler;
import android.content.ComponentName;
import android.os.Bundle;
import android.view.View;
import android.widget.RadioGroup;
import android.widget.SeekBar;
import android.widget.Switch;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity {
private static final int JOB_ID = 0;
private JobScheduler mScheduler;
// Switches for setting job options.
private Switch mDeviceIdleSwitch;
private Switch mDeviceChargingSwitch;
// Override deadline seekbar.
private SeekBar mSeekBar;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mDeviceIdleSwitch = findViewById(R.id.idleSwitch);
mDeviceChargingSwitch = findViewById(R.id.chargingSwitch);
mSeekBar = findViewById(R.id.seekBar);
final TextView seekBarProgress = findViewById(R.id.seekBarProgress);
mScheduler = (JobScheduler) getSystemService(JOB_SCHEDULER_SERVICE);
// Updates the TextView with the value from the seekbar.
mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int i, boolean b) {
if (i > 0) {
seekBarProgress.setText(getString(R.string.seconds, i));
} else {
seekBarProgress.setText(R.string.not_set);
}
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
});
}
public void scheduleJob(View view) {
RadioGroup networkOptions = findViewById(R.id.networkOptions);
int selectedNetworkID = networkOptions.getCheckedRadioButtonId();
int selectedNetworkOption = JobInfo.NETWORK_TYPE_NONE;
int seekBarInteger = mSeekBar.getProgress();
boolean seekBarSet = seekBarInteger > 0;
switch (selectedNetworkID) {
case R.id.noNetwork:
selectedNetworkOption = JobInfo.NETWORK_TYPE_NONE;
break;
case R.id.anyNetwork:
selectedNetworkOption = JobInfo.NETWORK_TYPE_ANY;
break;
case R.id.wifiNetwork:
selectedNetworkOption = JobInfo.NETWORK_TYPE_UNMETERED;
break;
}
ComponentName serviceName = new ComponentName(getPackageName(),
NotificationJobService.class.getName());
JobInfo.Builder builder = new JobInfo.Builder(JOB_ID, serviceName)
.setRequiredNetworkType(selectedNetworkOption)
.setRequiresDeviceIdle(mDeviceIdleSwitch.isChecked())
.setRequiresCharging(mDeviceChargingSwitch.isChecked());
if (seekBarSet) {
builder.setOverrideDeadline(seekBarInteger * 1000);
}
boolean constraintSet = selectedNetworkOption
!= JobInfo.NETWORK_TYPE_NONE
|| mDeviceChargingSwitch.isChecked()
|| mDeviceIdleSwitch.isChecked()
|| seekBarSet;
if (constraintSet) {
JobInfo myJobInfo = builder.build();
mScheduler.schedule(myJobInfo);
Toast.makeText(this, R.string.job_scheduled, Toast.LENGTH_SHORT)
.show();
} else {
Toast.makeText(this, R.string.no_constraint_toast,
Toast.LENGTH_SHORT).show();
}
}
public void cancelJobs(View view) {
if (mScheduler != null) {
mScheduler.cancelAll();
mScheduler = null;
Toast.makeText(this, R.string.jobs_canceled, Toast.LENGTH_SHORT)
.show();
}
}
}
My NotificationJobService class code:
package com.example.notificationscheduler;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.job.JobParameters;
import android.app.job.JobService;
import android.content.Intent;
import android.graphics.Color;
import androidx.core.app.NotificationCompat;
public class NotificationJobService extends JobService {
// Notification channel ID.
private static final String PRIMARY_CHANNEL_ID =
"primary_notification_channel";
// Notification manager.
NotificationManager mNotifyManager;
@Override
public boolean onStartJob(JobParameters jobParameters) {
// Create the notification channel.
createNotificationChannel();
// Set up the notification content intent to launch the app when clicked
PendingIntent contentPendingIntent = PendingIntent.getActivity
(this, 0, new Intent(this, MainActivity.class),
PendingIntent.FLAG_UPDATE_CURRENT);
NotificationCompat.Builder builder = new NotificationCompat.Builder
(this, PRIMARY_CHANNEL_ID)
.setContentTitle(getString(R.string.job_service))
.setContentText(getString(R.string.job_running))
.setContentIntent(contentPendingIntent)
.setSmallIcon(R.drawable.ic_job_running)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setDefaults(NotificationCompat.DEFAULT_ALL)
.setAutoCancel(true);
mNotifyManager.notify(0, builder.build());
return false;
}
@Override
public boolean onStopJob(JobParameters jobParameters) {
return false;
}
public void createNotificationChannel() {
// Create a notification manager object.
mNotifyManager =
(NotificationManager) getSystemService(NOTIFICATION_SERVICE);
// Notification channels are only available in OREO and higher.
// So, add a check on SDK version.
if (android.os.Build.VERSION.SDK_INT >=
android.os.Build.VERSION_CODES.O) {
// Create the NotificationChannel with all the parameters.
NotificationChannel notificationChannel = new NotificationChannel
(PRIMARY_CHANNEL_ID,
getString(R.string.job_service_notification),
NotificationManager.IMPORTANCE_HIGH);
notificationChannel.enableLights(true);
notificationChannel.setLightColor(Color.RED);
notificationChannel.enableVibration(true);
notificationChannel.setDescription
(getString(R.string.notification_channel_description));
mNotifyManager.createNotificationChannel(notificationChannel);
}
}
}
strings.xml code:
<resources>
<string name="app_name">NotificationScheduler</string>
<string name="seconds">%1$d s</string>
<string name="required_network_type">Network Type Required:</string>
<string name="none">None</string>
<string name="any">Any</string>
<string name="wifi">Wifi</string>
<string name="requires">Requires:</string>
<string name="device_idle">Device Idle</string>
<string name="device_charging">Device Charging</string>
<string name="override_deadline">Override Deadline:</string>
<string name="not_set">Not set</string>
<string name="schedule_job">Schedule Job</string>
<string name="cancel_jobs">Cancel Jobs</string>
<string name="job_service">Job Service</string>
<string name="job_running">Your Job ran to completion!</string>
<string name="job_service_notification">Job Service notification</string>
<string name="notification_channel_description">Notifications from Job Service</string>
<string name="job_scheduled">Job Scheduled, will run when the constraints are met</string>
<string name="no_constraint_toast">Please set at least one constraint</string>
<string name="jobs_canceled">Jobs Canceled</string>
</resources>
activity_main.xml
<LinearLayout
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"
android:orientation="vertical"
android:padding="@dimen/layout_padding">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/view_margin"
android:text="@string/required_network_type"
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
<RadioGroup
android:id="@+id/networkOptions"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/view_margin"
android:orientation="horizontal">
<RadioButton
android:id="@+id/noNetwork"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="true"
android:text="@string/none" />
<RadioButton
android:id="@+id/anyNetwork"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/any" />
<RadioButton
android:id="@+id/wifiNetwork"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/wifi" />
</RadioGroup>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/view_margin"
android:text="@string/requires"
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="4dp"
android:orientation="horizontal">
<Switch
android:id="@+id/idleSwitch"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/view_margin"
android:text="@string/device_idle" />
<Switch
android:id="@+id/chargingSwitch"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/view_margin"
android:text="@string/device_charging" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="@dimen/view_margin"
android:orientation="horizontal">
<TextView
android:id="@+id/seekBarLabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/override_deadline"
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
<TextView
android:id="@+id/seekBarProgress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/not_set"
android:textAppearance="@style/TextAppearance.AppCompat.Subhead" />
</LinearLayout>
<SeekBar
android:id="@+id/seekBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="@dimen/view_margin" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_margin="@dimen/view_margin"
android:onClick="scheduleJob"
android:text="@string/schedule_job" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_margin="@dimen/view_margin"
android:onClick="cancelJobs"
android:text="@string/cancel_jobs" />
</LinearLayout>
dimens.xml
<resources>
<dimen name="layout_padding">16dp</dimen>
<dimen name="view_margin">4dp</dimen>
</resources>