Skip to content

Commit 13dfd28

Browse files
authored
Merge pull request #99 from Natifishman/feature/performance-enhancements
Performance Optimization and Memory Management
2 parents 3209597 + 5d53039 commit 13dfd28

29 files changed

+1546
-86
lines changed

app/build.gradle.kts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ plugins {
77
id("org.jetbrains.kotlin.android")
88
id("org.jetbrains.kotlin.plugin.compose")
99
id("com.google.gms.google-services")
10+
id("com.google.firebase.crashlytics")
1011
id("com.diffplug.spotless") version "7.2.1"
1112
id("com.google.android.libraries.mapsplatform.secrets-gradle-plugin")
1213
id("com.getkeepsafe.dexcount") version "4.0.0" // For APK analysis
@@ -87,6 +88,11 @@ android {
8788
// Disable Crashlytics in debug builds
8889
buildConfigField("boolean", "ENABLE_CRASHLYTICS", "false")
8990

91+
// Disable Crashlytics collection for debug builds
92+
// configure<com.google.firebase.crashlytics.buildtools.gradle.CrashlyticsExtension> {
93+
// mappingFileUploadEnabled = false
94+
// }
95+
9096
proguardFiles(
9197
getDefaultProguardFile("proguard-android-optimize.txt"),
9298
"proguard-rules.pro",
@@ -103,6 +109,11 @@ android {
103109
// Enable Crashlytics in release builds
104110
buildConfigField("boolean", "ENABLE_CRASHLYTICS", "true")
105111

112+
// Enable Crashlytics collection for release builds
113+
// configure<com.google.firebase.crashlytics.buildtools.gradle.CrashlyticsExtension> {
114+
// mappingFileUploadEnabled = true
115+
// }
116+
106117
// ProGuard/R8 configuration with optimization
107118
proguardFiles(
108119
getDefaultProguardFile("proguard-android-optimize.txt"),
@@ -312,6 +323,10 @@ dependencies {
312323
// Firebase App Check for security
313324
implementation("com.google.firebase:firebase-appcheck-playintegrity:17.1.1")
314325
implementation("com.google.firebase:firebase-appcheck-debug:17.1.1")
326+
327+
// Firebase Crashlytics for crash reporting (only in release builds)
328+
implementation("com.google.firebase:firebase-crashlytics:19.0.3")
329+
implementation("com.google.firebase:firebase-analytics:22.1.2")
315330

316331
// --- Google Services ---
317332
implementation(libs.gms.play.services.auth)

app/src/main/java/com/example/partymaker/PartyApplication.java

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
import com.google.firebase.appcheck.FirebaseAppCheck;
2222
import com.google.firebase.appcheck.playintegrity.PlayIntegrityAppCheckProviderFactory;
2323
import com.google.firebase.appcheck.debug.DebugAppCheckProviderFactory;
24+
import com.google.firebase.crashlytics.FirebaseCrashlytics;
25+
import com.example.partymaker.utils.media.ImageOptimizationManager;
2426

2527
/** Application class for PartyMaker. Initializes repositories and other app-wide components. */
2628
public class PartyApplication extends Application {
@@ -48,6 +50,9 @@ public void onCreate() {
4850

4951
// Initialize Firebase App Check for security
5052
initializeAppCheck();
53+
54+
// Initialize Crashlytics
55+
initializeCrashlytics();
5156

5257
// Initialize Firebase references
5358
DBRef.init();
@@ -93,6 +98,13 @@ public void onCreate() {
9398
// Log memory info
9499
Log.d(TAG, "Initial memory usage: " + MemoryManager.getDetailedMemoryInfo());
95100

101+
// Setup global exception handler
102+
setupGlobalExceptionHandler();
103+
104+
// Optimize image loading configuration
105+
ImageOptimizationManager.optimizeGlideConfiguration(this);
106+
Log.d(TAG, "Image loading optimized");
107+
96108
// End application initialization timing
97109
PerformanceMonitor.trackMemoryUsage("Application.onCreate.end");
98110
PerformanceMonitor.endTiming("Application.onCreate");
@@ -173,6 +185,91 @@ private void initializeAppCheck() {
173185
}
174186
}
175187

188+
/**
189+
* Initializes Firebase Crashlytics for crash reporting.
190+
*/
191+
private void initializeCrashlytics() {
192+
try {
193+
FirebaseCrashlytics crashlytics = FirebaseCrashlytics.getInstance();
194+
195+
// Enable/disable based on build type
196+
crashlytics.setCrashlyticsCollectionEnabled(BuildConfig.ENABLE_CRASHLYTICS);
197+
198+
if (BuildConfig.ENABLE_CRASHLYTICS) {
199+
Log.d(TAG, "Crashlytics enabled for crash reporting");
200+
201+
// Set user properties
202+
crashlytics.setUserId("user_" + System.currentTimeMillis());
203+
crashlytics.setCustomKey("app_version", BuildConfig.VERSION_NAME);
204+
crashlytics.setCustomKey("debug_mode", BuildConfig.DEBUG);
205+
206+
// Test log (only in debug)
207+
if (BuildConfig.DEBUG) {
208+
crashlytics.log("PartyApplication initialized successfully");
209+
}
210+
} else {
211+
Log.d(TAG, "Crashlytics disabled for debug builds");
212+
}
213+
214+
} catch (Exception e) {
215+
Log.e(TAG, "Failed to initialize Crashlytics", e);
216+
}
217+
}
218+
219+
/**
220+
* Sets up global exception handler for uncaught exceptions.
221+
*/
222+
private void setupGlobalExceptionHandler() {
223+
// Store the original handler before replacing it
224+
final Thread.UncaughtExceptionHandler originalHandler = Thread.getDefaultUncaughtExceptionHandler();
225+
226+
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
227+
@Override
228+
public void uncaughtException(@NonNull Thread thread, @NonNull Throwable throwable) {
229+
Log.e(TAG, "Uncaught exception in thread: " + thread.getName(), throwable);
230+
231+
// Log crash information
232+
Log.e(TAG, "Crash details - Message: " + throwable.getMessage());
233+
Log.e(TAG, "Crash details - Cause: " + (throwable.getCause() != null ? throwable.getCause().toString() : "None"));
234+
235+
// Send to Crashlytics if enabled
236+
try {
237+
if (BuildConfig.ENABLE_CRASHLYTICS) {
238+
FirebaseCrashlytics crashlytics = FirebaseCrashlytics.getInstance();
239+
crashlytics.recordException(throwable);
240+
crashlytics.log("Uncaught exception in thread: " + thread.getName());
241+
}
242+
} catch (Exception e) {
243+
Log.e(TAG, "Failed to report crash to Crashlytics", e);
244+
}
245+
246+
// In debug builds, show additional information
247+
if (BuildConfig.DEBUG) {
248+
Log.e(TAG, "Stack trace: ", throwable);
249+
Log.e(TAG, "Thread state: " + thread.getState());
250+
Log.e(TAG, "Memory info: " + MemoryManager.getDetailedMemoryInfo());
251+
}
252+
253+
// Attempt graceful cleanup
254+
try {
255+
MemoryManager.getInstance().emergencyCleanup();
256+
} catch (Exception e) {
257+
Log.e(TAG, "Error during emergency cleanup", e);
258+
}
259+
260+
// Call the original handler to terminate the app
261+
if (originalHandler != null) {
262+
originalHandler.uncaughtException(thread, throwable);
263+
} else {
264+
// Fallback termination
265+
System.exit(1);
266+
}
267+
}
268+
});
269+
270+
Log.d(TAG, "Global exception handler configured");
271+
}
272+
176273
/**
177274
* Sets up memory monitoring for debug builds.
178275
*/
@@ -216,5 +313,7 @@ public void onLowMemory() {
216313
public void onTrimMemory(int level) {
217314
super.onTrimMemory(level);
218315
MemoryManager.getInstance().emergencyCleanup();
316+
// Also trim image cache memory
317+
ImageOptimizationManager.trimMemory(this, level);
219318
}
220319
}

app/src/main/java/com/example/partymaker/ui/adapters/GroupAdapter.java

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,13 @@ public GroupAdapter(Context context, OnGroupClickListener listener) {
5252
@Override
5353
public GroupViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
5454
View view = LayoutInflater.from(context).inflate(R.layout.item_group, parent, false);
55+
// Reduce overdraw by setting background only where needed
56+
view.setWillNotDraw(false);
57+
// Disable child clipping for better performance
58+
if (view instanceof ViewGroup) {
59+
((ViewGroup) view).setClipChildren(false);
60+
((ViewGroup) view).setClipToPadding(false);
61+
}
5562
return new GroupViewHolder(view);
5663
}
5764

@@ -67,6 +74,28 @@ public void onBindViewHolder(@NonNull GroupViewHolder holder, int position) {
6774
// Items will appear immediately without animation interference
6875
}
6976
}
77+
78+
@Override
79+
public void onBindViewHolder(@NonNull GroupViewHolder holder, int position, @NonNull java.util.List<Object> payloads) {
80+
if (payloads.isEmpty()) {
81+
// Full bind
82+
onBindViewHolder(holder, position);
83+
} else {
84+
// Partial bind for better performance - only update what changed
85+
Group group = getItem(position);
86+
if (group != null) {
87+
for (Object payload : payloads) {
88+
if ("name".equals(payload)) {
89+
holder.setGroupName(group);
90+
} else if ("date".equals(payload)) {
91+
holder.setGroupDate(group);
92+
} else if ("image".equals(payload)) {
93+
holder.loadGroupImage(group.getGroupKey());
94+
}
95+
}
96+
}
97+
}
98+
}
7099

71100
/**
72101
* Ensures view is in completely normal state to prevent spacing issues
@@ -135,6 +164,21 @@ class GroupViewHolder extends RecyclerView.ViewHolder {
135164
groupNameTextView = itemView.findViewById(R.id.tvGroupName);
136165
groupDateTextView = itemView.findViewById(R.id.tvGroupDate);
137166
groupImageView = itemView.findViewById(R.id.imgGroupPicture);
167+
168+
// Optimize text views to reduce GPU operations
169+
if (groupNameTextView != null) {
170+
groupNameTextView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
171+
groupNameTextView.setIncludeFontPadding(false);
172+
}
173+
if (groupDateTextView != null) {
174+
groupDateTextView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
175+
groupDateTextView.setIncludeFontPadding(false);
176+
}
177+
// Image view uses software layer for static images
178+
if (groupImageView != null) {
179+
groupImageView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
180+
}
181+
138182
setupClickListeners();
139183
}
140184

app/src/main/java/com/example/partymaker/ui/features/auth/LoginActivity.java

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
import androidx.annotation.NonNull;
2020
import androidx.annotation.Nullable;
2121
import androidx.appcompat.app.ActionBar;
22-
import androidx.appcompat.app.AppCompatActivity;
22+
import com.example.partymaker.ui.base.BaseActivity;
2323
import androidx.lifecycle.ViewModelProvider;
2424
import com.example.partymaker.R;
2525
import com.example.partymaker.data.api.NetworkManager;
@@ -53,7 +53,7 @@
5353
* @version 2.0
5454
* @since 1.0
5555
*/
56-
public class LoginActivity extends AppCompatActivity {
56+
public class LoginActivity extends BaseActivity {
5757

5858
private static final class Config {
5959
static final String LOG_TAG = "LoginActivity";
@@ -612,4 +612,30 @@ void clearAnimations() {
612612
}
613613
}
614614
}
615+
616+
@Override
617+
protected void clearActivityReferences() {
618+
// Clear all UI references
619+
// Clear UI references
620+
emailEditText = null;
621+
passwordEditText = null;
622+
loginButton = null;
623+
registerButton = null;
624+
resetPasswordButton = null;
625+
googleSignInButton = null;
626+
progressBar = null;
627+
rememberMeCheckbox = null;
628+
aboutButton = null;
629+
rootView = null;
630+
631+
// Clear ViewModels and other objects
632+
authViewModel = null;
633+
firebaseAuth = null;
634+
635+
// Clear animation manager
636+
if (animationManager != null) {
637+
animationManager.clearAnimations();
638+
animationManager = null;
639+
}
640+
}
615641
}

app/src/main/java/com/example/partymaker/ui/features/auth/RegisterActivity.java

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
import android.widget.TextView;
2727
import android.widget.Toast;
2828
import androidx.appcompat.app.ActionBar;
29-
import androidx.appcompat.app.AppCompatActivity;
29+
import com.example.partymaker.ui.base.BaseActivity;
3030
import androidx.core.app.NotificationCompat;
3131
import androidx.core.content.ContextCompat;
3232
import androidx.lifecycle.ViewModelProvider;
@@ -48,7 +48,7 @@
4848
* Activity for user registration, including form validation, animations, and notifications. Handles
4949
* user input, password strength, and registration logic.
5050
*/
51-
public class RegisterActivity extends AppCompatActivity {
51+
public class RegisterActivity extends BaseActivity {
5252
/** Notification channel ID. */
5353
private static final String CHANNEL_ID = "registration_channel";
5454

@@ -839,4 +839,29 @@ private void sendSuccessNotification(String username) {
839839
(NotificationManager) getSystemService(NOTIFICATION_SERVICE);
840840
notificationManager.notify(1, builder.build());
841841
}
842+
843+
@Override
844+
protected void clearActivityReferences() {
845+
// Clear UI references
846+
tilEmail = null;
847+
tilUsername = null;
848+
tilPassword = null;
849+
etEmail = null;
850+
etUsername = null;
851+
etPassword = null;
852+
btnRegister = null;
853+
btnPress = null;
854+
imgRegister = null;
855+
headerCard = null;
856+
formCard = null;
857+
celebrationLayout = null;
858+
passwordStrengthBar = null;
859+
passwordStrengthText = null;
860+
formProgressText = null;
861+
progressIndicator = null;
862+
progressBar = null;
863+
864+
// Clear ViewModels
865+
authViewModel = null;
866+
}
842867
}

app/src/main/java/com/example/partymaker/ui/features/auth/ResetPasswordActivity.java

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
import androidx.annotation.NonNull;
2222
import androidx.annotation.Nullable;
2323
import androidx.appcompat.app.ActionBar;
24-
import androidx.appcompat.app.AppCompatActivity;
24+
import com.example.partymaker.ui.base.BaseActivity;
2525
import androidx.lifecycle.ViewModelProvider;
2626
import com.example.partymaker.R;
2727
import com.example.partymaker.viewmodel.auth.ResetPasswordViewModel;
@@ -39,7 +39,7 @@
3939
* @version 2.0
4040
* @since 1.0
4141
*/
42-
public class ResetPasswordActivity extends AppCompatActivity {
42+
public class ResetPasswordActivity extends BaseActivity {
4343

4444
private static final class Config {
4545
static final String LOG_TAG = "ResetPasswordActivity";
@@ -261,6 +261,28 @@ private void cleanupResources() {
261261
resetPasswordViewModel = null;
262262
}
263263

264+
@Override
265+
protected void clearActivityReferences() {
266+
// Clear UI components
267+
lightThemeButton = null;
268+
darkThemeButton = null;
269+
resetPasswordButton = null;
270+
helpButton = null;
271+
hideInstructionsButton = null;
272+
emailInputField = null;
273+
resetLayout = null;
274+
forgotPasswordTextView = null;
275+
helpTextView = null;
276+
instructionsTextView = null;
277+
hideInstructionsTextView = null;
278+
cakeImageView = null;
279+
280+
// Clear dependencies and managers
281+
resetPasswordViewModel = null;
282+
themeManager = null;
283+
emailValidator = null;
284+
}
285+
264286
// Inner Classes and Text Watcher
265287
private class EmailValidationTextWatcher implements TextWatcher {
266288
@Override

0 commit comments

Comments
 (0)