Skip to main content

7 posts tagged with "framerate"

View All Tags

SOLVING FRAME RATE ISSUES AND APP HANGS IN SWIFTUI IOS APPS

Published: · Last updated: · 7 min read
Appxiom Team
Mobile App Performance Experts

Developing a smooth and responsive iOS app is crucial for providing a great user experience. Frame rate issues and app hangs can be frustrating for users and can lead to negative reviews and decreased app usage.

In this blog post, we will explore common causes of frame rate issues and app hangs in SwiftUI iOS apps and provide solutions and code examples to address them.

Understanding Frame Rate Issues

Frame rate issues occur when an app struggles to render frames at the desired rate, usually 60 frames per second (FPS) on most iOS devices. When the frame rate drops, animations become choppy, and the app feels less responsive. There are several common reasons for frame rate issues:

  • Inefficient View Updates: SwiftUI's declarative nature encourages frequent view updates. If not optimized, this can lead to excessive rendering and reduced frame rates.

  • Heavy Computation on the Main Thread: Performing CPU-intensive tasks on the main thread can block the UI, making the app feel unresponsive.

  • Large Images and Assets: Loading or rendering large images or assets can consume significant memory and processing power, leading to frame rate drops.

Solving Frame Rate Issues in SwiftUI

1. Optimize View Updates

You can optimize view updates by:

  • Using the .onAppear and .onDisappear modifiers to load data only when necessary.

  • Implementing the .id modifier to identify views uniquely and avoid unnecessary updates.

  • Reducing the complexity of SwiftUI view hierarchies.

Example:

struct ContentView: View {
var body: some View {
Text("Optimize your views")
.onAppear {
// Load data when the view appears
loadData()
}
}
}

2. Offload Heavy Computation

Move CPU-intensive tasks to background threads using DispatchQueue or Combine. Ensure that UI updates occur on the main thread.

Using DispatchQueue:

DispatchQueue.global().async {
// Perform heavy computation
let result = performHeavyComputation()

DispatchQueue.main.async {
// Update the UI on the main thread
self.resultLabel = result
}
}

Combine is a powerful framework for handling asynchronous and event-driven code in Swift. You can use Combine to perform background operations in SwiftUI seamlessly. In this example, we'll demonstrate how to use Combine to execute a background operation and update the SwiftUI view when the operation completes.

Let's say you want to fetch some data from a network API in the background and update your SwiftUI view when the data is ready. Here's a step-by-step guide:

  1. Import Combine in your SwiftUI view file:
import SwiftUI
import Combine
  1. Define a ViewModel to handle your data and background operations. Create an ObservableObject class that will hold your data and expose a publisher for notifying view updates.
class MyViewModel: ObservableObject {
@Published var data: [YourDataType] = [] // Replace YourDataType with the actual data type you're using
private var cancellables: Set<AnyCancellable> = []

func fetchData() {
// Simulate a background network request
fetchDataFromNetwork()
.receive(on: DispatchQueue.main) // Ensure updates are on the main thread
.sink { completion in
// Handle completion or errors if needed
} receiveValue: { [weak self] newData in
self?.data = newData
// Update the data when received
}
.store(in: &cancellables)
}

private func fetchDataFromNetwork() -> AnyPublisher<[YourDataType], Error> {
// Implement your network request logic here and return a Combine publisher
// For example, you can use URLSession's dataTaskPublisher
let url = URL(string: "https://your-api-url.com/data")!
return URLSession.shared.dataTaskPublisher(for: url)
.map(\.data)
.decode(type: [YourDataType].self, decoder: JSONDecoder())
.eraseToAnyPublisher()
}
}

Replace YourDataType with the actual type of data you're fetching from the network.

  1. Create a SwiftUI view that observes the changes in your ViewModel and triggers the background operation:
struct ContentView: View {
@ObservedObject private var viewModel = MyViewModel()

var body: some View {
VStack {
if viewModel.data.isEmpty {
Text("Loading...")
} else {
List(viewModel.data, id: \.self) { item in
// Display your data here
Text(item.name)
// Replace with your data properties
}
}
}
.onAppear {
viewModel.fetchData()
// Trigger the background operation when the view appears
}
}
}

In this SwiftUI view, the @ObservedObject property wrapper observes changes to the viewModel, and the onAppear modifier triggers the background operation by calling viewModel.fetchData() when the view appears.

Now, your SwiftUI view will fetch data from the network in the background using Combine and update the view when the data is ready, providing a smooth and responsive user experience.

3. Efficiently Manage Images and Assets

Load images lazily and use asset catalogs for managing image resources. Resize images to appropriate dimensions to reduce memory usage.

In SwiftUI, you can load images lazily using the AsyncImage view. AsyncImage allows you to load and display images asynchronously, which is especially useful for large images or images fetched from the network. Here's how you can use AsyncImage to load images lazily in SwiftUI:

import SwiftUI

struct LazyLoadingImageView: View {
let imageURL: URL
var body: some View {
AsyncImage(url: imageURL) { phase in
switch phase {
case .empty:
// Placeholder while loading (optional)
ProgressView()
case .success(let image):
// Successfully loaded image
image
.resizable()
.scaledToFit()
case .failure(_):
// Handle the failure (e.g., show an error message)
Image(systemName: "xmark.octagon")
.resizable()
.scaledToFit()
.foregroundColor(.red)
@unknown default:
// Handle other unknown states
Text("Unknown state")
}
}
}
}

In the code above:

  • AsyncImage is used to load the image asynchronously from the specified URL.

  • The closure inside AsyncImage receives a Phase parameter, which represents the current state of the image loading process.

  • In the .empty phase, you can display a placeholder (e.g., a ProgressView) to indicate that the image is being loaded.

  • In the .success phase, you can display the loaded image, making it resizable and scaling it to fit the available space.

  • In the .failure phase, you can handle the failure by displaying an error image or a message.

  • The @unknown default case is used to handle any unknown states that might be introduced in future SwiftUI versions.

To use the LazyLoadingImageView in your SwiftUI view, simply provide the URL of the image you want to load:

struct ContentView: View {
var body: some View {
LazyLoadingImageView(imageURL: URL(string: "https://example.com/image.jpg")!)
.frame(width: 200, height: 200)
}
}

Make sure to replace "https://example.com/image.jpg" with the actual URL of the image you want to load.

With AsyncImage, you can efficiently load and display images in a lazy manner, ensuring a smooth user experience, especially when dealing with large images or images from remote sources.

Addressing App Hangs

App hangs occur when the app becomes unresponsive for 250 milli seconds or more due to various reasons, such as blocking the main thread or network requests taking too long. Here are some strategies to prevent app hangs:

1. Use Background Threads for Network Requests

Perform network requests on background threads to avoid blocking the main thread. Combine or URLSession can be used for this purpose.

Example:

let cancellable = URLSession.shared.dataTaskPublisher(for: url)
.map(\.data)
.decode(type: MyModel.self, decoder: JSONDecoder())
.receive(on: DispatchQueue.main)
.sink(receiveCompletion: { _ in }) { data in
// Process data and update UI
}

2. Implement Error Handling

Handle errors gracefully, especially in asynchronous operations, to prevent app hangs and crashes.

do {
let result = try performRiskyOperation()
// Handle the result
} catch {
// Handle the error
}

3. Use Xcode Instruments and APM tools

Use Xcode's Instruments to profile your app's performance, identify bottlenecks, and monitor memory usage. Debugging tools like LLDB can help trace and fix specific issues causing frame rate issues and app hangs.

APM tools are a very much helpful in detecting frame rate issues and App Hangs. Appxiom is such an APM tool that helps in detecting these issues and provides all relevant data points to the developer to fix the issue.

Conclusion

Frame rate issues and app hangs can significantly impact the user experience of your SwiftUI iOS app. By optimizing view updates, offloading heavy computation, efficiently managing assets, and addressing app hangs through proper threading and error handling, you can create a smooth and responsive app that users will love.

Remember that performance optimization is an ongoing process. Regularly test your app on different devices and keep an eye on performance metrics to ensure a consistently great user experience.

FRAME RATE ISSUES IN FLUTTER APPS AND HOW TO SOLVE THEM

Published: · Last updated: · 4 min read
Appxiom Team
Mobile App Performance Experts

Flutter, Google's open-source UI development framework, has gained immense popularity among developers for its cross-platform capabilities and smooth performance. However, like any software development framework, Flutter apps may encounter frame rate issues that can impact user experience.

In this blog, we will explore the common causes of frame rate issues in Flutter apps and provide effective solutions to mitigate them.

Understanding Frame Rate Issues in Flutter Apps

The frame rate of a Flutter app refers to the number of frames or screen updates displayed per second. The standard frame rate for smooth user experience is 60 frames per second (fps). If an app fails to achieve this frame rate consistently, it can result in stuttering animations, sluggish responsiveness, and an overall degraded user experience.

In Android, frame rate issues may manifest as App Not Responding (ANR) if the UI Thread gets blocked for 5000 milliseconds or more. If the UI Frames take 700 milliseconds or more to render it is a Frozen Frame situation and if it takes 16 milliseconds or more it is a Slow Frame situation.

In iOS, if the UI Thread is stuck for 250 milliseconds or more it is an App Hang, also called App Freeze, situation.

Common Causes of Frame Rate Issues

1. Expensive Widget Rebuilds

class MyExpensiveWidget extends StatelessWidget {
final ExpensiveData data;

const MyExpensiveWidget({required this.data});

@override
Widget build(BuildContext context) {
// Widget build logic that might be expensive
return ...;
}
}

To optimize widget rebuilds, use const constructors whenever possible. By using const, Flutter can efficiently skip the widget rebuild if the constructor parameters haven't changed.

2. Inefficient Animations

class MyAnimationWidget extends StatefulWidget {
@override
_MyAnimationWidgetState createState() => _MyAnimationWidgetState();
}

class _MyAnimationWidgetState extends State<MyAnimationWidget>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _animation;

@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(milliseconds: 500),
vsync: this,
);
_animation = Tween(begin: 0.0, end: 1.0).animate(_controller);
_controller.forward();
}

@override
void dispose() {
_controller.dispose();
super.dispose();
}

@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _animation,
builder: (context, child) {
// Widget build logic using the animation value
return ...;
},
);
}
}

To optimize animations, use lightweight animations like Tween animations instead of heavy ones like Hero animations. Properly dispose of animation controllers to release resources and avoid unnecessary computations. Implement animation caching techniques, such as pre-loading and reusing animations, to reduce performance impact.

3. Inadequate Caching and Data Fetching

class MyDataFetcher {
static final Map<String, dynamic> _cache = {};

static Future<dynamic> fetchData(String url) async {
if (_cache.containsKey(url)) {
return _cache[url];
} else {
final response = await http.get(Uri.parse(url));
final data = json.decode(response.body);
_cache[url] = data;
return data;
}
}
}

To optimize caching and data fetching, implement proper caching strategies. Utilize Flutter's built-in caching mechanisms, such as cached_network_image, to minimize repeated image downloads. Implement pagination techniques to fetch data incrementally instead of in one large chunk.

4. Simplify Layouts

class MyComplexLayout extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
child: Column(
children: [
Expanded(
child: Row(
children: [
Flexible(child: Container()),
Flexible(child: Container()),
],
),
),
Expanded(
child: Container(),
),
],
),
);
}
}

To simplify layouts, minimize nested layouts and unnecessary constraints. Use appropriate layout widgets based on specific requirements. Avoid excessive use of Expanded and Flexible widgets when other layout techniques like SizedBox or AspectRatio can achieve the desired results.

Use App Performance Monitoring (APM) Tools

Monitoring the frame rate of a Flutter app is crucial for maintaining optimal performance and delivering a smooth user experience. APM tools provide valuable insights into the app's rendering performance, allowing developers to identify and address frame rate issues effectively.

Two widely used tools for frame rate monitoring in Flutter are Firebase Performance Monitoring and Appxiom.

Conclusion

Frame rate issues in Flutter apps can negatively impact the user experience, leading to reduced engagement and user satisfaction. By optimizing widget rebuilds, animations, caching and data fetching, as well as simplifying layouts, developers can ensure a smooth and responsive UI.

Remember to profile your app, optimize animations, simplify layouts, and follow best practices to address frame rate issues effectively. Use APM tools to continuously monitor app performance including frame rate issues. With careful attention to performance optimization, Flutter can deliver exceptional user experiences across various platforms.

DETECTING ANR IN ANDROID APPS USING FIREBASE CRASHLYTICS AND APPXIOM.

Published: · Last updated: · 2 min read
Appxiom Team
Mobile App Performance Experts

Firebase Crashlytics is used in most of the mobile apps to detect crashes. It also reports ANRs in Android with a detailed stacktrace.

Appxiom is a bug detection tool for Android and iOS apps. It captures a range of bugs including Crashes, ANRs, Memory Leaks, Memory Spikes, Abnormal Memory Usage, Frozen Frames, Slow Frames, HTTP API call issues, Screen Load Delays, and much more.

By definition ANR is triggered when the UI Thread gets blocked for 5 seconds or more. Appxiom detects and reports ANRs as and when the issue happens. It provides a chronologically ordered Activity Trail and a detailed Stacktrace that helps in locating where the ANR was triggered.

In Firebase ANRs get captured only when the user opts to Force Quit the app and then reopen it. This selection is done when the dialog message pops up asking if the app should Force Quit or it should wait to see if the UI Thread comes back. That means if the user waits and the UI Thread comes back to normalcy the ANR will not be reported. Also if the user decides to not come back the Firebase will not report the ANR.

ANR detection in Appxiom & FirebaseA detailed write up on how to use Appxiom to detect the root cause of ANR is available here https://www.blog.appxiom.com/post/detecting-and-fixing-anr-in-android-apps.

To know more about how Appxiom can help you in detecting bugs, visit https://appxiom.com. BTW, the tool works seamlessly in development, testing and live phases.

ANALYSIS OF IMPACT OF BUGS IN ANDROID, IOS AND WATCHOS APPS IS NOW AVAILABLE IN APPXIOM.

Published: · Last updated: · One min read
Appxiom Team
Mobile App Performance Experts

Performance Analytics has been a demand from our customers since Appxiom 1.0. Now with the release of Appxiom 6.0 the mobile app developers will get a clear understanding about how the bugs are impacting the installation base. The data is presented graphically and provides insights into the percentage and absolute number of android, watchOS and iOS devices that got affected.

Fig 1: Analysis of bugs and the impact on mobilesThe impact of memory leaks, abnormal memory usages, slow frames and frozen frames, ANR and App Hang, UI thread blocks, network issues, and all other bugs are now presented in a tangible way. Also bugs can be ordered based on the number of devices that are affected. This helps mobile app developers to prioritise bugs and to understand where they need to focus on to improve the performance of the app.

Appxiom 6.0 comes as Android SDK, iOS framework and watchOS framework. It's currently in closed beta, and is expected to go live by 21st of February, 2022. If you would like to request access for Appxiom 6.0, click here.

DETECTING AND FIXING ANR IN ANDROID APPS.

Published: · Last updated: · 4 min read
Don Peter
Cofounder and CTO, Appxiom

Any developer who has ever written an Android app must have faced the age old issue of 'App Not Responding' popularly known by the acronym ANR. ANRs are UI/Main thread blocks that prevail for more than 5000 milliseconds. Fixing ANRs require three steps much like any other issue.

  1. Get to know the existence of the issue.

  2. Get accurate data points to identify the root cause of the issue.

  3. Get the issue fixed and release a patch version fast.

Unlike crashes, the problem that we developers face here is in step 2. We at Appxiom figured out a method that will help us get accurate data points to fix ANRs in Android apps.

Let us get started.

Integrate Appxiom.

The first step is to add Appxiom SDK to the android application. SDK is capable of automatically detecting and reporting UI thread blocks in Android apps out of the box. SDK comes as a Gradle library, and the integration will take only couple of minutes.

Steps to integrate

  1. Add maven tag to your project level build.gradle file.
buildscript {
repositories {
}
dependencies {
classpath 'com.android.tools.build:gradle:x.x.x'
}
}
allprojects {
repositories {
jcenter()
maven {
url "https://appxiomcoreandroid.s3.amazonaws.com/release"
}
}
}
  1. Add dependencies to the app level build.gradle file and sync the project with gradle files.
releaseImplementation('com.appxiom:appxiomcore:x.x.x@aar') {
transitive = true;
}

debugImplementation('com.appxiom:appxiomdebug:x.x.x@aar') {
transitive = true;
}
  1. Finally, initialize the SDK.
Ax.init(this);

Java Code: In the onCreate function of Application class.

Ax.init(this)

Kotlin Code: In the onCreate function of Application class.

Run the app using Exerciser Monkey.

Exerciser Monkey is an adb command. It executes random touches and gestures in the app, just as if a monkey is using the app. This will help in detecting abnormalities in the app that regular tests may miss.

It is easy to execute Monkey tool. Run this command from the terminal and wait for the execution to complete. Please feel free to customize these command options.

$ ./adb shell monkey -v-v --ignore-crashes --ignore-timeouts --ignore-security-exceptions --throttle 100 --pct-touch 35 --pct-motion 40 --pct-nav 0 --pct-majornav 0 --pct-appswitch 5 --pct-anyevent 5 --pct-trackball 5 --pct-syskeys 0 --pct-pinchzoom 5 --bugreport 11000

Before executing the command, it is a good practise to keep the app pinned to the screen. This is to make sure that the monkey command does not accidentally close your app.

Once the execution is complete you will get detailed report with the number of ANRs encountered. But as I said at the beginning, it is real hard to identify the exact reason for the ANRs purely from these bug reports.

Appxiom reports ANR as and when they occur, unlike Firebase Crashlytics which report only those ANRs which resulted in the user Force Quitting the app and restarting it.

Identifying root cause of ANR

So, how do we identify the root cause of these issues. Head over to Appxiom dashboard and look for all issues reported.

ANR reported in Appxiom dashboardStacktrace available with each ANR issue report will help us to identify the exact line that triggered the ANR and to fix them.

Custom ANR threshold to detect shorter UI thread blocks.

Now, let us take this to the next level. We can use the @Observe annotation provided by the SDK to set a custom threshold in detecting UI thread blocks. The value can be anywhere between 500 and 5000 milliseconds. Once set, SDK will report UI thread blocks detected above the set threshold. The default value is 3000 milliseconds.

@Observe(ANRThresholdInMilliseconds = 1000)
public class BlogApp extends Application {
...

Java Code - Setting custom ANR threshold

@Observe(ANRThresholdInMilliseconds = 1000)
class BlogApp: Application() {
...

Kotlin Code - Setting custom ANR threshold

It is important to note that, monkey tool is not mandatory for Appxiom to detect ANRs. We run the app in any device or simulator and it will detect issues.

One of our customers tells us on how the new improved ANR detection feature helped them identify the root cause. Read it here https://www.blog.appxiom.com/post/identifying-root-cause-of-anr-in-android-apps.

Visit https://docs.appxiom.com for detailed documentation. To know more about Appxiom, visit https://appxiom.com.

Watch this space for more updates.

STATE OF BUGS IN MOBILE APPS - APPXIOM REPORT FOR YEAR 2020

Published: · Last updated: · 2 min read
Robin Alex Panicker
Cofounder and CPO, Appxiom

Year 2020 started with the fear of pandemic induced economic recession. But it seems technology domain was less unaffected than expected. Reason for this is the increased dependency on technology. This increase in demand reflected in the number of bug reports Appxiom handled in 2020. Appxiom captured 464 Million bug reports which is 45 times the count for 2019.

We analysed the bug types to get an understanding about the common issues reported in mobile apps. We decided to share this data to help developers to focus more on such issues and to improve the quality of mobile apps.

Bug stats for iOS apps

In 2020 Appxiom captured 138 Million bug reports from 624 iOS apps built on Objective C or Swift. This is from an install base of 1.3M devices.

API related bugs are the most common bugs in iOS and contribute 42.9% of all issues reported, followed by memory leaks at 25%. Crashes contribute 14.3% of bugs. Frame skips came next at 9.5%.

Bugs in iOS apps

Bug stats for Android apps

Appxiom captured 324 Million bug reports in 2020 from 1853 android apps built on Java or Kotlin. Total install base is 2.1M devices.

Memory leaks are the most common bugs in android and contribute 29.7% of all issues reported, followed by screen load delays at 25.4%. Crashes contribute 23.7%% of bugs. API bugs are at 9.5%.

Bugs in Android apps

Conclusion

While we do not claim this to be an exact representation of state of mobile app bugs across millions of apps out there, this data has some clear indicators. Higher density of memory related bugs is a major concern, and one of the reasons is that a good number of the memory leaks go unnoticed during development phase. API bugs and crashes are issues that could be identified before going in production, but looks like a not so small number of them make it to live apps.

HOW TO DETECT AND FIX FRAME RATE ISSUES IN IOS APPS.

Published: · Last updated: · 4 min read
Don Peter
Cofounder and CTO, Appxiom

One of the hard to find issues developers come across in iOS apps is frame rate related issues. These issues are often overlooked, which tend to affect the user experience and smooth functioning of iOS apps.

Before we dive head first, let us take a look at some of the basic terminologies.

Screen refresh rate and Frame rate

Every device with a screen has something called screen refresh rate. It is the number of times the screen gets refreshed in a second which is measured in Hertz (denoted Hz) and is hardware-specific.

Whereas frame rate refers to the number of times UI frames are updated within a second. This is measured in frames per second or fps.

Higher the screen refresh rate, smoother will be the user experience for events like scrolling, gaming animation, switching over between apps and other UI events.

Screen refresh rate in a device remains constant which is generally around 60 Hz to 120 Hz depending on the type of device, while the frame rate can vary. Ideally, both screen refresh rate and frame rate should be the same in order for the user to experience smooth animations and to have fluid interactions with the app.

But there may arise situations where the screen content may not get updated as often as it is capable of. This is called frame rate dips. Frame rate dip occurs in an iOS app when resource-intensive tasks are executed in the UI thread. In such situations, frame rate in an iOS device with a screen refresh rate of 60 Hz can become less than 60 fps.

Reasons for frame rate bugs in iOS apps

Normally, frame rate bugs occur in iOS apps when the UI thread/ main thread, that is responsible for updating the user interface (UI) and processing user input events, gets stuck for a few milliseconds or even seconds.

Some of the scenarios that can cause UI thread block are when blending pixels with multiple values, off-screen rendering or even adding misaligned images to a view.

I am planning to write a separate blog post on each of these individual scenarios that cause frame rate bugs next.

So how do we detect frame rate issues?

Appxiom is an automated bug reporting tool that is capable of detecting frame rate issues without the need for profiling the entire application.

With basic integration, Appxiom development mode SDK is capable of monitoring UI/main thread. Whenever the UI thread gets stuck in iOS apps, Appxiom detects and reports it. For more details, please visit our documentation pages on Objective-C and Swift.

Initializing Appxiom in Objective-C

#import <AppxiomCore/Ax.h>

- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:
(NSDictionary *)launchOptions {

[Ax initialize:application];
return true;
}

Initializing Appxiom in Swift

import AppxiomCoreSwift

func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions:
[UIApplicationLaunchOptionsKey: Any]?) -> Bool {
Ax.initialize(application)
return true
}

Developers can also use inbuilt tools like Time profiler and Core Animation in Xcode Instruments to identify the root cause of frame rate issues and avoid them in iOS apps.

The only way to avoid frame rate issues in iOS apps is by ensuring that the UI thread has very less burden and all time-consuming processes are executed in separate threads. Most of the modern apps target 60 frames per second which is equivalent to 16.67 milliseconds per frame. Hence developers need to make sure to set app drawables at this stable frame rate.

Apart from frame rate issues, Appxiom also detects API call issues, memory leaks, abnormal memory usage, function failures, and delays along with custom issues and crashes. To know more about how Appxiom detects and reports ANR issues (an extension of frame rate issues) in Android apps, please read our blog post on How to detect and fix ANR issues in Android apps.

Visit appxiom.com to know more about Appxiom. Detailed documentation is available at docs.appxiom.com.