These are the coding standards and best practices for the SimpleBatteryNotifier Android project.
- Build Configuration
- Code Style
- Null Safety
- Variables
- Code Organization
- Android Best Practices
- Performance
- Comments & Documentation
- minSdk: 26 (Android 8.0 Oreo) - Can be bumped if required
- targetSdk: 36 (Latest)
- Gradle: 9.2.1
- JDK: 25
- Use AndroidX libraries (not legacy support libraries)
- Keep dependencies up to date
- Document why each dependency is needed
Always use curly brackets, even for single-line statements.
// ❌ BAD
if(condition) doSomething();
if(condition)
doSomething();
// ✅ GOOD
if (condition) {
doSomething();
}Why? Prevents bugs when adding additional lines later.
Use isNull() and nonNull() instead of == null and != null.
import static java.util.Objects.isNull;
import static java.util.Objects.nonNull;
// ❌ BAD
if(object == null) {}
if(object != null) {}
// ✅ GOOD
if(isNull(object)) {}
if(nonNull(object)) {}Why? More readable and follows modern Java conventions.
Name methods to match how they're used - avoid always inverting boolean methods.
// ❌ BAD - Method always called inverted
private boolean hasPermission() {
return checkPermission();
}
if(!hasPermission()) { // Always inverted!
return;
}
// ✅ GOOD - Method name matches usage
private boolean lacksPermission() {
return !checkPermission();
}
if(lacksPermission()) { // No inversion needed
return;
}Why? Reduces cognitive load and makes code more readable. If you're always inverting a method, the name is wrong.
Guideline: If your IDE warns "Calls to method X are always inverted", rename the method.
- Maximum line width: 160 characters
- Don't over-wrap everything
- Break long lines at logical points (method calls, operators)
// ✅ GOOD - Reasonable line length
final String value = prefs.getString(context.getString(R.string.key), defaultValue);
// ✅ GOOD - Break at logical point when too long
final String longValue = sharedPreferences.getString(
context.getString(R.string.very_long_preference_key_name),
context.getString(R.string.very_long_default_value)
);Never use fully qualified names in code - use imports instead.
// ❌ BAD
android.content.Intent intent=new android.content.Intent();
// ✅ GOOD
import android.content.Intent;
Intent intent = new Intent();// ❌ BAD - Can crash if service is null
NotificationManager manager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
manager.notify(id, notification); // NPE possible!
// ✅ GOOD
final NotificationManager manager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
if(nonNull(manager)) {
manager.notify(id, notification);
}// ❌ BAD
Bitmap bitmap = BitmapFactory.decodeResource(resources, R.drawable.image);
imageView.setImageBitmap(bitmap); // NPE if decode fails!
// ✅ GOOD
final Bitmap bitmap = BitmapFactory.decodeResource(resources, R.drawable.image);
if(nonNull(bitmap)) {
imageView.setImageBitmap(bitmap);
}// ❌ BAD
String name = "John";
int age = 25;
// ✅ GOOD
final String name = "John";
final int age = 25;Why? Improves readability and prevents accidental reassignment.
// ❌ BAD
final String name = getName();
final int age = getAge();
// ... 50 lines of code ...
System.out.println(name);
// ✅ GOOD
// ... 50 lines of code ...
final String name = getName();
System.out.println(name);Return early to reduce nesting depth.
// ❌ BAD - Deep nesting
public void process(String input) {
if (input != null) {
if (input.length() > 0) {
if (isValid(input)) {
doSomething(input);
}
}
}
}
// ✅ GOOD - Early returns
public void process(final String input) {
if (isNull(input)) {
return;
}
if (input.isEmpty()) {
return;
}
if (!isValid(input)) {
return;
}
doSomething(input);
}Each method/class should do ONE thing.
// ❌ BAD - Does too much
public void processUserDataAndSendEmail(User user) {
validateUser(user);
saveToDatabase(user);
sendWelcomeEmail(user);
logActivity(user);
}
// ✅ GOOD - Separate concerns
public void processUser(final User user) {
validateUser(user);
saveToDatabase(user);
}
public void sendWelcomeEmail(final User user) {
// Email logic only
}Extract repeated code into reusable methods.
// ❌ BAD - Repeated code
final NotificationManager manager1 = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
manager1.notify(id1, notification1);
final NotificationManager manager2 = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
manager2.notify(id2, notification2);
// ✅ GOOD - Extracted to method
private void sendNotification(final Context context, final int id, final Notification notification) {
final NotificationManager manager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
if (nonNull(manager)) {
manager.notify(id, notification);
}
}- Maximum method length: ~50 lines
- If longer, break into smaller methods
- Each method should have a clear, single purpose
// ❌ BAD
private static final String UNUSED_CONSTANT = "value";
private int unusedVariable;
public void unusedMethod() {
// Never called
}
// ✅ GOOD
// Delete all unused code - don't comment it out// ❌ BAD - No permission check
notificationManager.notify(id, notification);
// ✅ GOOD - Check permission first (Android 13+)
private boolean hasNotificationPermission(final Context context) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
return ContextCompat.checkSelfPermission(context, Manifest.permission.POST_NOTIFICATIONS)
== PackageManager.PERMISSION_GRANTED;
}
return true;
}
if(hasNotificationPermission(context)) {
notificationManager.notify(id, notification);
}// ❌ BAD - Deprecated
new Handler().post(runnable);
startActivityForResult(intent, requestCode);
// ✅ GOOD - Modern
new Handler(Looper.getMainLooper()).post(runnable);
activityResultLauncher.launch(intent);// ❌ BAD - Not thread-safe
private static boolean isActive;
// ✅ GOOD - Thread-safe
private static volatile boolean isActive;// ❌ BAD
new Thread(() ->{
doBackgroundWork();
}).start();
// ✅ GOOD
private static final ExecutorService executor = Executors.newSingleThreadExecutor();
executor.execute(() -> doBackgroundWork());// ❌ BAD - Decode every time
public void showNotification() {
Bitmap icon = BitmapFactory.decodeResource(resources, R.mipmap.ic_launcher);
notification.setLargeIcon(icon);
}
// ✅ GOOD - Cache the bitmap
private static Bitmap cachedIcon;
private Bitmap getIcon() {
if (isNull(cachedIcon)) {
cachedIcon = BitmapFactory.decodeResource(resources, R.mipmap.ic_launcher);
}
return cachedIcon;
}Call methods once and store the result - don't call repeatedly in the same scope.
// ❌ BAD - Calls getResources() 8 times!
if(usbCharge) {
charger = context.getResources().getString(R.string.charger_connected_usb);
chargerSource = context.getResources().getString(R.string.charger_usb);
} else if(acCharge) {
charger = context.getResources().getString(R.string.charger_connected_ac);
chargerSource =context.getResources().getString(R.string.charger_ac);
} else if(wirelessCharge){
charger = context.getResources().getString(R.string.charger_connected_wireless);
chargerSource = context.getResources().getString(R.string.charger_wireless);
}
// ✅ GOOD - Call once, reuse the reference
final Resources resources = context.getResources();
if(usbCharge) {
charger = resources.getString(R.string.charger_connected_usb);
chargerSource = resources.getString(R.string.charger_usb);
} else if(acCharge) {
charger = resources.getString(R.string.charger_connected_ac);
chargerSource = resources.getString(R.string.charger_ac);
}else if (wirelessCharge) {
charger = resources.getString(R.string.charger_connected_wireless);
chargerSource = resources.getString(R.string.charger_wireless);
}Why? Reduces method call overhead and improves readability.
Common cases:
context.getResources()- Extract tofinal Resources resourcesgetActivity()- Extract tofinal Activity activityview.getContext()- Extract tofinal Context context- Repeated property access in loops
// ❌ BAD
public void method1() {
NotificationManager mgr = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
mgr.notify(1, n1);
}
public void method2() {
NotificationManager mgr = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
mgr.notify(2, n2);
}
// ✅ GOOD - Extract to helper
private NotificationManager getNotificationManager(final Context context) {
return (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
}When reviewing, flag these:
- ❌ Multiple bitmap decodes
- ❌ Repeated database queries in loops
- ❌ Synchronous I/O on main thread
- ❌ Inefficient algorithms (O(n²) when O(n) exists)
- ❌ Memory leaks (static context references)
// ❌ BAD - Obvious comments
// Get the name
final String name = getName();
// Loop through users
for(User user :users) {
// ❌ BAD - Outdated
// Created by John Doe on 01/01/2015
// ✅ GOOD - No comment needed for obvious code
final String name = getName();// ✅ GOOD - Explain WHY, not WHAT
// Handle overnight time ranges (e.g., 8:00 PM to 6:00 AM)
if(endHour <=startHour) {
endCal.add(Calendar.DATE, 1);
}
// ✅ GOOD - Document complex business logic
/**
* Check if the device is in Do Not Disturb mode.
* Returns false if the ZEN_MODE setting is not available (pre-API 23).
*/
private boolean isInDoNotDisturbMode() {
try {
return ZEN_MODE_IMPORTANT_INTERRUPTIONS == Settings.Global.getInt(resolver, ZEN_MODE);
} catch (Settings.SettingNotFoundException e) {
return false;
}
}/**
* Send a battery status notification
*
* @param context The application context
* @param type Notification type (CRITICAL_TYPE, WARNING_TYPE, or FULL_LEVEL_TYPE)
*/
public static void sendNotification(final Context context, final int type) {
// Implementation
}// ❌ BAD
boolean withInTime; // Should be "within"
String recieve; // Should be "receive"
// ✅ GOOD
boolean withinTime;
String receive;// ❌ BAD
int tmp;
String s;
List<String> list1;
// ✅ GOOD
int batteryLevel;
String userName;
List<String> notificationChannels;When reviewing code, check:
- Uses
isNull()/nonNull()instead of== null/!= null - All curly brackets present (even single-line)
- Variables marked
finalwhere appropriate - No FQNs (uses imports)
- Line width ≤ 160 characters
- Early returns instead of deep nesting
- No code duplication (DRY)
- Methods ≤ 50 lines
- All null checks in place
- Permissions checked (Android 13+)
- Modern APIs used (no deprecated)
- Thread-safe for static fields
- No performance issues
- Comments are helpful, not obvious
- Spelling is correct
- No unused code/imports
- Follows SOLID principles
Remember:
- Safety first - Null checks, permissions, thread safety
- Performance matters - Cache expensive operations
- Readability counts - Clear names, early returns, proper formatting
- Quality over quantity - Small focused methods beat large complex ones
- Modern practices - Use latest Android APIs and patterns
When in doubt:
- If it looks complex, simplify it
- If it's repeated, extract it
- If it's unclear, document it
- If it's unused, delete it
Last updated: 2025-12-07