DEV Community

Angel G. Olloqui for Playtomic

Posted on • Originally published at angelolloqui.com

Adding multi device previews in Xcode and Android Studio

Developing with SwiftUI and Jetpack Compose is very user-friendly. Both frameworks offer a highly readable declarative syntax and a live preview feature that updates in real-time while coding. This live preview allows developers to easily configure various device settings, although it can be a bit cumbersome to specify multiple device factors. At Playtomic, we have created a simple utility for both platforms that allows us to run our previews on a predefined set of devices. Here's what we did:

Android

This platform was the easy one, as it already supports aggregated annotations out of the box. All you need to do is include somewhere in your project:

@Preview(showSystemUi = true, device = Devices.PIXEL_4_XL, name = "Pixel 4 XL")   // big xxxhdpi
@Preview(showSystemUi = true, device = Devices.NEXUS_5, name = "Nexus 5")   // small-medium xxhdpi
annotation class MultiDevicePreview
Enter fullscreen mode Exit fullscreen mode

And then just replace the usage of @Preview by @MultiDevicePreview in your previews:

@MultiDevicePreview
@Composable
private fun AMultiDevicePreview() {
    Text("Multidevice preview")
}
Enter fullscreen mode Exit fullscreen mode

Screenshot from preview in Android Studio using multiple device previews

iOS

Things become a bit more complicated for iOS. Firstly, Xcode only recognizes previews that implement the PreviewProvider protocol. As a result, extending the interface with our custom version of MultiDevicePreviewProvider doesn't work. Instead, we created a second protocol to tackle this problem:

public protocol MultiDevicePreview {
    associatedtype DevicePreviews : View
    @ViewBuilder @MainActor static var devicePreviews: DevicePreviews { get }

    @MainActor static var devices: [PreviewDevice] { get }
    @MainActor static var previewName: String? { get }
}
Enter fullscreen mode Exit fullscreen mode

And then, we provided a default implementation of the previews for those using the new protocol:

public extension PreviewProvider where Self: MultiDevicePreview {
    static var previews: some View {
        ForEach(devices) { device in
            AnyView(devicePreviews)
                .previewDevice(device)
                .previewDisplayName([previewName, device.rawValue].compactMap { $0 }.joined(separator: " - "))
        }
    }

    static var devices: [PreviewDevice] { PreviewDevice.allCases }
    static var previewName: String? { String(describing: Self.self) }
}
Enter fullscreen mode Exit fullscreen mode

We also declared some predefined list of devices to simplify the management:

extension PreviewDevice {
    public static let iPhone14 = PreviewDevice("iPhone 14")
    public static let iPhone14Max = PreviewDevice("iPhone 14 Pro Max")
    public static let iPhoneSE = PreviewDevice("iPhone SE (3rd generation)")
    public static let allCases = [iPhone14, iPhone14Max, iPhoneSE]
}

extension PreviewDevice: Identifiable {
    public var id: String {
        rawValue
    }
}
Enter fullscreen mode Exit fullscreen mode

With all of the above, you can make use of multidevice previews by conforming your preview to MultiDevicePreview and replace the method previews by devicePreviews like:

struct AMultiDevicePreview: PreviewProvider, MultiDevicePreview {
    static var devicePreviews: some View {
        Text("multidevice preview text")
    }
}
Enter fullscreen mode Exit fullscreen mode

Scheenshot from Xcode using multiple device previews

Not a huge difference, but a nice small addition to the toolkit!

Top comments (0)