Android Articles / Blogs / Perficient https://blogs.perficient.com/tag/android/ Expert Digital Insights Wed, 26 Feb 2025 21:50:16 +0000 en-US hourly 1 https://blogs.perficient.com/files/favicon-194x194-1-150x150.png Android Articles / Blogs / Perficient https://blogs.perficient.com/tag/android/ 32 32 30508587 Kotlin Multiplatform vs. React Native vs. Flutter: Building Your First App https://blogs.perficient.com/2025/02/26/kotlin-multiplatform-vs-react-native-vs-flutter-building-your-first-app/ https://blogs.perficient.com/2025/02/26/kotlin-multiplatform-vs-react-native-vs-flutter-building-your-first-app/#respond Wed, 26 Feb 2025 21:50:16 +0000 https://blogs.perficient.com/?p=377508

Choosing the right framework for your first cross-platform app can be challenging, especially with so many great options available. To help you decide, let’s compare Kotlin Multiplatform (KMP), React Native, and Flutter by building a simple “Hello World” app with each framework. We’ll also evaluate them across key aspects like setup, UI development, code sharing, performance, community, and developer experience. By the end, you’ll have a clear understanding of which framework is best suited for your first app.

Building a “Hello World” App

1. Kotlin Multiplatform (KMP)

Kotlin Multiplatform allows you to share business logic across platforms while using native UI components. Here’s how to build a “Hello World” app:

Steps:

  1. Set Up the Project:
    • Install Android Studio and the Kotlin Multiplatform Mobile plugin.
    • Create a new KMP project using the “Mobile Library” template.
  2. Shared Code:In the shared module, create a Greeting class with a function to return “Hello World”.
    // shared/src/commonMain/kotlin/Greeting.kt
    class Greeting {
        fun greet(): String {
            return "Hello, World!"
        }
    }
  3. Platform-Specific UIs:For Android, use Jetpack Compose or XML layouts in the androidApp module. For iOS, use SwiftUI or UIKit in the iosApp module.Android (Jetpack Compose):
    // androidApp/src/main/java/com/example/androidApp/MainActivity.kt
    class MainActivity : ComponentActivity() {
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContent {
                Text(text = Greeting().greet())
            }
        }
    }

    iOS (SwiftUI):

    // iosApp/iosApp/ContentView.swift
    struct ContentView: View {
        var body: some View {
            Text(Greeting().greet())
        }
    }
  4. Run the App:Build and run the app on Android and iOS simulators/emulators.

Pros and Cons:

Pros:

  • Native performance and look.
  • Shared business logic reduces code duplication.

Cons:

  • Requires knowledge of platform-specific UIs (Jetpack Compose for Android, SwiftUI/UIKit for iOS).
  • Initial setup can be complex.

2. React Native

React Native allows you to build cross-platform apps using JavaScript and React. Here’s how to build a “Hello World” app:

Steps:

  1. Set Up the Project:
    • Install Node.js and the React Native CLI.
    • Create a new project:
      npx react-native init HelloWorldApp
  2. Write the Code:Open App.js and replace the content with the following:
    import React from 'react';
    import { Text, View } from 'react-native';
    
    const App = () => {
        return (
            <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
                <Text>Hello, World!</Text>
            </View>
        );
    };
    
    export default App;
  3. Run the App:Start the Metro bundler:
    npx react-native start

    Run the app on Android or iOS:

    npx react-native run-android
    npx react-native run-ios

Pros and Cons:

Pros:

  • Easy setup and quick development.
  • Hot reload for instant updates.

Cons:

  • Performance may suffer for complex apps due to the JavaScript bridge.
  • Limited native look and feel.

3. Flutter

Flutter is a UI toolkit for building natively compiled apps for mobile, web, and desktop using Dart. Here’s how to build a “Hello World” app:

Steps:

  1. Set Up the Project:
    • Install Flutter SDK and Android Studio/VS Code.
    • Create a new project:
      flutter create hello_world_app
  2. Write the Code:Open lib/main.dart and replace the content with the following:
    import 'package:flutter/material.dart';
    
    void main() {
        runApp(MyApp());
    }
    
    class MyApp extends StatelessWidget {
        @override
        Widget build(BuildContext context) {
            return MaterialApp(
                home: Scaffold(
                    appBar: AppBar(title: Text('Hello World App')),
                    body: Center(child: Text('Hello, World!')),
                ),
            );
        }
    }
  3. Run the App:Run the app on Android or iOS:
    flutter run

Pros and Cons:

Pros:

  • Single codebase for UI and business logic.
  • Excellent performance and rich UI components.

Cons:

  • Larger app size compared to native apps.
  • Requires learning Dart.

Comparing the Frameworks

1. Initial Setup

  • KMP: Moderate setup complexity, especially for iOS. Requires configuring Gradle files and platform-specific dependencies.
  • React Native: Easy setup with tools like Expo and React Native CLI.
  • Flutter: Smoothest setup with the Flutter CLI and flutter doctor command.

Best option: Flutter (for ease of initial setup).

2. UI Development

  • KMP: Platform-specific UIs (Jetpack Compose for Android, SwiftUI/UIKit for iOS). Offers native flexibility but requires separate UI code.
  • React Native: Declarative UI with JSX. Powerful but can feel like a middle ground between native and custom rendering.
  • Flutter: Widget-based system for consistent cross-platform UIs. Highly customizable but requires learning Dart.

Best option: A tie between KMP (for native UI flexibility) and Flutter (for cross-platform consistency).

3. Code Sharing

  • KMP: Excels at sharing business logic while allowing native UIs.
  • React Native: High code sharing but may require platform-specific code for advanced features.
  • Flutter: High code sharing for both UI and business logic but requires Dart.

Best option: Kotlin Multiplatform (for its focus on sharing business logic).

4. Performance

  • KMP: Native performance due to native UIs and compiled shared code.
  • React Native: Good performance but can struggle with complex UIs due to the JavaScript bridge.
  • Flutter: Excellent performance, often close to native, but may not match native performance in all scenarios.

Winner: Kotlin Multiplatform (for native performance).

5. Community and Ecosystem

  • KMP: Growing community backed by JetBrains. Kotlin ecosystem is mature.
  • React Native: Large and active community with a rich ecosystem.
  • Flutter: Thriving community with strong Google support.

Best option: React Native (for its large and mature community), but Flutter is a close contender.

6. Developer Experience

  • KMP: Gentle learning curve for Kotlin developers but requires platform-specific UI knowledge.
  • React Native: Familiar for JavaScript/React developers but may require native mobile knowledge.
  • Flutter: Excellent developer experience with hot reload and comprehensive documentation.

Best option: Flutter (for its excellent developer experience and tooling).

7. AI-Assisted Development Speed

With the rise of AI tools like GitHub Copilot, ChatGPT, Gemini, Claude, etc.. Developers can significantly speed up app development. Let’s evaluate how each framework benefits from AI assistance:

  • KMP: AI tools can help generate Kotlin code for shared logic and even platform-specific UIs. However, the need for platform-specific knowledge may limit the speed gains.
  • React Native: JavaScript is widely supported by AI tools, making it easy to generate boilerplate code, components, and even entire screens. The large ecosystem also means AI can suggest relevant libraries and solutions.
  • Flutter: Dart is less commonly supported by AI tools compared to JavaScript, but Flutter’s widget-based system is highly structured, making it easier for AI to generate consistent and functional code.

Best option: React Native (due to JavaScript’s widespread support in AI tools).

The resolution:

There’s no one-size-fits-all answer. The best choice depends on your priorities:

    • Prioritize Performance and Native UI: Choose Kotlin Multiplatform.
    • Prioritize Speed of Development and a Large Community: Choose React Native.
    • Prioritize Ease of Use, Cross-Platform Consistency, and Fast Development: Choose Flutter.

For Your First App:

  • Simple App, Fast Development: Flutter is an excellent choice. Its ease of setup, hot reload, and comprehensive widget system will get you up and running quickly.
  • Existing Kotlin/Android Skills, Focus on Shared Logic: Kotlin Multiplatform allows you to leverage your existing knowledge while sharing a significant portion of your codebase.
  • Web Developer, Familiar with React: React Native is a natural fit, allowing you to utilize your web development skills for mobile development.

Conclusion

Each framework has its strengths and weaknesses, and the best choice depends on your team’s expertise, project requirements, and long-term goals. For your first app, consider starting with Flutter for its ease of use and fast development, React Native if you’re a web developer, or Kotlin Multiplatform if you’re focused on performance and native UIs.

Try building a simple app with each framework to see which one aligns best with your preferences and project requirements.

References

  1. Kotlin Multiplatform Documentation: https://kotlinlang.org/docs/multiplatform.html
  2. React Native Documentation: https://reactnative.dev/docs/getting-started
  3. Flutter Documentation: https://flutter.dev/docs
  4. JetBrains Blog on KMP: https://blog.jetbrains.com/kotlin/
  5. React Native Community: https://github.com/react-native-community
  6. Flutter Community: https://flutter.dev/community

 

]]>
https://blogs.perficient.com/2025/02/26/kotlin-multiplatform-vs-react-native-vs-flutter-building-your-first-app/feed/ 0 377508
Migrating from MVP to Jetpack Compose: A Step-by-Step Guide for Android Developers https://blogs.perficient.com/2025/02/03/migrating-from-mvp-to-jetpack-compose-a-step-by-step-guide-for-android-developers/ https://blogs.perficient.com/2025/02/03/migrating-from-mvp-to-jetpack-compose-a-step-by-step-guide-for-android-developers/#comments Mon, 03 Feb 2025 15:30:02 +0000 https://blogs.perficient.com/?p=376701

Migrating an Android App from MVP to Jetpack Compose: A Step-by-Step Guide

Jetpack Compose is Android’s modern toolkit for building native UI. It simplifies and accelerates UI development by using a declarative approach, which is a significant shift from the traditional imperative XML-based layouts. If you have an existing Android app written in Kotlin using the MVP (Model-View-Presenter) pattern with XML layouts, fragments, and activities, migrating to Jetpack Compose can bring numerous benefits, including improved developer productivity, reduced boilerplate code, and a more modern UI architecture.

In this article, we’ll walk through the steps to migrate an Android app from MVP with XML layouts to Jetpack Compose. We’ll use a basic News App to explain in detail how to migrate all layers of the app. The app has two screens:

  1. A News List Fragment to display a list of news items.
  2. A News Detail Fragment to show the details of a selected news item.

We’ll start by showing the original MVP implementation, including the Presenters, and then migrate the app to Jetpack Compose step by step. We’ll also add error handling, loading states, and use Kotlin Flow instead of LiveData for a more modern and reactive approach.

1. Understand the Key Differences

Before diving into the migration, it’s essential to understand the key differences between the two approaches:

  • Imperative vs. Declarative UI: XML layouts are imperative, meaning you define the UI structure and then manipulate it programmatically. Jetpack Compose is declarative, meaning you describe what the UI should look like for any given state, and Compose handles the rendering.
  • MVP vs. Compose Architecture: MVP separates the UI logic into Presenters and Views. Jetpack Compose encourages a more reactive and state-driven architecture, often using ViewModel and State Hoisting.
  • Fragments and Activities: In traditional Android development, Fragments and Activities are used to manage UI components. In Jetpack Compose, you can replace most Fragments and Activities with composable functions.

2. Plan the Migration

Migrating an entire app to Jetpack Compose can be a significant undertaking. Here’s a suggested approach:

  1. Start Small: Begin by migrating a single screen or component to Jetpack Compose. This will help you understand the process and identify potential challenges.
  2. Incremental Migration: Jetpack Compose is designed to work alongside traditional Views, so you can migrate your app incrementally. Use ComposeView in XML layouts or AndroidView in Compose to bridge the gap.
  3. Refactor MVP to MVVM: Jetpack Compose works well with the MVVM (Model-View-ViewModel) pattern. Consider refactoring your Presenters into ViewModels.
  4. Replace Fragments with Composable Functions: Fragments can be replaced with composable functions, simplifying navigation and UI management.
  5. Add Error Handling and Loading States: Ensure your app handles errors gracefully and displays loading states during data fetching.
  6. Use Kotlin Flow: Replace LiveData with Kotlin Flow for a more modern and reactive approach.

3. Set Up Jetpack Compose

Before starting the migration, ensure your project is set up for Jetpack Compose:

  1. Update Gradle Dependencies:
    Add the necessary Compose dependencies to your build.gradle file:

    android {
        ...
        buildFeatures {
            compose true
        }
        composeOptions {
            kotlinCompilerExtensionVersion '1.5.3'
        }
    }
    
    dependencies {
        implementation 'androidx.activity:activity-compose:1.8.0'
        implementation 'androidx.compose.ui:ui:1.5.4'
        implementation 'androidx.compose.material:material:1.5.4'
        implementation 'androidx.compose.ui:ui-tooling-preview:1.5.4'
        implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.6.2'
        implementation 'androidx.navigation:navigation-compose:2.7.4' // For navigation
        implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.6.2' // For Flow
        implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3' // For Flow
    }
  2. Enable Compose in Your Project:
    Ensure your project is using the correct Kotlin and Android Gradle plugin versions.

4. Original MVP Implementation

a. News List Fragment and Presenter

The NewsListFragment displays a list of news items. The NewsListPresenter fetches the data and updates the view.

NewsListFragment.kt

class NewsListFragment : Fragment(), NewsListView {

    private lateinit var presenter: NewsListPresenter
    private lateinit var adapter: NewsListAdapter

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        val view = inflater.inflate(R.layout.fragment_news_list, container, false)
        val recyclerView = view.findViewById<RecyclerView>(R.id.recyclerView)
        adapter = NewsListAdapter { newsItem -> presenter.onNewsItemClicked(newsItem) }
        recyclerView.adapter = adapter
        recyclerView.layoutManager = LinearLayoutManager(context)
        presenter = NewsListPresenter(this)
        presenter.loadNews()
        return view
    }

    override fun showNews(news: List<NewsItem>) {
        adapter.submitList(news)
    }

    override fun showLoading() {
        // Show loading indicator
    }

    override fun showError(error: String) {
        // Show error message
    }
}

NewsListPresenter.kt

class NewsListPresenter(private val view: NewsListView) {

    fun loadNews() {
        view.showLoading()
        // Simulate fetching news from a data source (e.g., API or local database)
        try {
            val newsList = listOf(
                NewsItem(id = 1, title = "News 1", summary = "Summary 1"),
                NewsItem(id = 2, title = "News 2", summary = "Summary 2")
            )
            view.showNews(newsList)
        } catch (e: Exception) {
            view.showError(e.message ?: "An error occurred")
        }
    }

    fun onNewsItemClicked(newsItem: NewsItem) {
        // Navigate to the news detail screen
        val intent = Intent(context, NewsDetailActivity::class.java).apply {
            putExtra("newsId", newsItem.id)
        }
        startActivity(intent)
    }
}

NewsListView.kt

interface NewsListView {
    fun showNews(news: List<NewsItem>)
    fun showLoading()
    fun showError(error: String)
}

b. News Detail Fragment and Presenter

The NewsDetailFragment displays the details of a selected news item. The NewsDetailPresenter fetches the details and updates the view.

NewsDetailFragment.kt

class NewsDetailFragment : Fragment(), NewsDetailView {

    private lateinit var presenter: NewsDetailPresenter

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        val view = inflater.inflate(R.layout.fragment_news_detail, container, false)
        presenter = NewsDetailPresenter(this)
        val newsId = arguments?.getInt("newsId") ?: 0
        presenter.loadNewsDetail(newsId)
        return view
    }

    override fun showNewsDetail(newsItem: NewsItem) {
        view?.findViewById<TextView>(R.id.title)?.text = newsItem.title
        view?.findViewById<TextView>(R.id.summary)?.text = newsItem.summary
    }

    override fun showLoading() {
        // Show loading indicator
    }

    override fun showError(error: String) {
        // Show error message
    }
}

NewsDetailPresenter.kt

class NewsDetailPresenter(private val view: NewsDetailView) {

    fun loadNewsDetail(newsId: Int) {
        view.showLoading()
        // Simulate fetching news detail from a data source (e.g., API or local database)
        try {
            val newsItem = NewsItem(id = newsId, title = "News $newsId", summary = "Summary $newsId")
            view.showNewsDetail(newsItem)
        } catch (e: Exception) {
            view.showError(e.message ?: "An error occurred")
        }
    }
}

NewsDetailView.kt

interface NewsDetailView {
    fun showNewsDetail(newsItem: NewsItem)
    fun showLoading()
    fun showError(error: String)
}

5. Migrate to Jetpack Compose

a. Migrate the News List Fragment

Replace the NewsListFragment with a composable function. The NewsListPresenter will be refactored into a NewsListViewModel.

NewsListScreen.kt

@Composable
fun NewsListScreen(viewModel: NewsListViewModel, onItemClick: (NewsItem) -> Unit) {
    val newsState by viewModel.newsState.collectAsState()

    when (newsState) {
        is NewsState.Loading -> {
            // Show loading indicator
            CircularProgressIndicator()
        }
        is NewsState.Success -> {
            val news = (newsState as NewsState.Success).news
            LazyColumn {
                items(news) { newsItem ->
                    NewsListItem(newsItem = newsItem, onClick = { onItemClick(newsItem) })
                }
            }
        }
        is NewsState.Error -> {
            // Show error message
            val error = (newsState as NewsState.Error).error
            Text(text = error, color = Color.Red)
        }
    }
}

@Composable
fun NewsListItem(newsItem: NewsItem, onClick: () -> Unit) {
    Card(
        modifier = Modifier
            .fillMaxWidth()
            .padding(8.dp)
            .clickable { onClick() }
    ) {
        Column(modifier = Modifier.padding(16.dp)) {
            Text(text = newsItem.title, style = MaterialTheme.typography.h6)
            Text(text = newsItem.summary, style = MaterialTheme.typography.body1)
        }
    }
}

NewsListViewModel.kt

class NewsListViewModel : ViewModel() {

    private val _newsState = MutableStateFlow<NewsState>(NewsState.Loading)
    val newsState: StateFlow<NewsState> get() = _newsState

    init {
        loadNews()
    }

    private fun loadNews() {
        viewModelScope.launch {
            _newsState.value = NewsState.Loading
            try {
                // Simulate fetching news from a data source (e.g., API or local database)
                val newsList = listOf(
                    NewsItem(id = 1, title = "News 1", summary = "Summary 1"),
                    NewsItem(id = 2, title = "News 2", summary = "Summary 2")
                )
                _newsState.value = NewsState.Success(newsList)
            } catch (e: Exception) {
                _newsState.value = NewsState.Error(e.message ?: "An error occurred")
            }
        }
    }
}

sealed class NewsState {
    object Loading : NewsState()
    data class Success(val news: List<NewsItem>) : NewsState()
    data class Error(val error: String) : NewsState()
}

b. Migrate the News Detail Fragment

Replace the NewsDetailFragment with a composable function. The NewsDetailPresenter will be refactored into a NewsDetailViewModel.

NewsDetailScreen.kt

@Composable
fun NewsDetailScreen(viewModel: NewsDetailViewModel) {
    val newsState by viewModel.newsState.collectAsState()

    when (newsState) {
        is NewsState.Loading -> {
            // Show loading indicator
            CircularProgressIndicator()
        }
        is NewsState.Success -> {
            val newsItem = (newsState as NewsState.Success).news
            Column(modifier = Modifier.padding(16.dp)) {
                Text(text = newsItem.title, style = MaterialTheme.typography.h4)
                Text(text = newsItem.summary, style = MaterialTheme.typography.body1)
            }
        }
        is NewsState.Error -> {
            // Show error message
            val error = (newsState as NewsState.Error).error
            Text(text = error, color = Color.Red)
        }
    }
}

NewsDetailViewModel.kt

class NewsDetailViewModel : ViewModel() {

    private val _newsState = MutableStateFlow<NewsState>(NewsState.Loading)
    val newsState: StateFlow<NewsState> get() = _newsState

    fun loadNewsDetail(newsId: Int) {
        viewModelScope.launch {
            _newsState.value = NewsState.Loading
            try {
                // Simulate fetching news detail from a data source (e.g., API or local database)
                val newsItem = NewsItem(id = newsId, title = "News $newsId", summary = "Summary $newsId")
                _newsState.value = NewsState.Success(newsItem)
            } catch (e: Exception) {
                _newsState.value = NewsState.Error(e.message ?: "An error occurred")
            }
        }
    }
}

sealed class NewsState {
    object Loading : NewsState()
    data class Success(val news: NewsItem) : NewsState()
    data class Error(val error: String) : NewsState()
}

6. Set Up Navigation

Replace Fragment-based navigation with Compose navigation:

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            NewsApp()
        }
    }
}

@Composable
fun NewsApp() {
    val navController = rememberNavController()
    NavHost(navController = navController, startDestination = "newsList") {
        composable("newsList") {
            val viewModel: NewsListViewModel = viewModel()
            NewsListScreen(viewModel = viewModel) { newsItem ->
                navController.navigate("newsDetail/${newsItem.id}")
            }
        }
        composable("newsDetail/{newsId}") { backStackEntry ->
            val viewModel: NewsDetailViewModel = viewModel()
            val newsId = backStackEntry.arguments?.getString("newsId")?.toIntOrNull() ?: 0
            viewModel.loadNewsDetail(newsId)
            NewsDetailScreen(viewModel = viewModel)
        }
    }
}

7. Test and Iterate

After migrating the screens, thoroughly test the app to ensure it behaves as expected. Use Compose’s preview functionality to visualize your UI:

@Preview(showBackground = true)
@Composable
fun PreviewNewsListScreen() {
    NewsListScreen(viewModel = NewsListViewModel(), onItemClick = {})
}

@Preview(showBackground = true)
@Composable
fun PreviewNewsDetailScreen() {
    NewsDetailScreen(viewModel = NewsDetailViewModel())
}

8. Gradually Migrate the Entire App

Once you’re comfortable with the migration process, continue migrating the rest of your app incrementally. Use ComposeView and AndroidView to integrate Compose with existing XML

]]>
https://blogs.perficient.com/2025/02/03/migrating-from-mvp-to-jetpack-compose-a-step-by-step-guide-for-android-developers/feed/ 1 376701
Unit Testing in Android Apps: A Deep Dive into MVVM https://blogs.perficient.com/2024/11/26/unit-testing-in-android-apps-a-deep-dive-into-mvvm/ https://blogs.perficient.com/2024/11/26/unit-testing-in-android-apps-a-deep-dive-into-mvvm/#respond Tue, 26 Nov 2024 19:56:40 +0000 https://blogs.perficient.com/?p=372567

Understanding Unit Testing

Unit testing is a crucial aspect of software development, especially in complex applications like Android apps. It involves testing individual units of code, such as methods or classes, in isolation. This ensures the correctness of each component, leading to a more robust and reliable application.

Why Unit Testing in MVVM?

The Model-View-ViewModel (MVVM) architectural pattern is widely adopted in Android app development. It separates the application into three distinct layers:

  • Model: Handles data logic and interacts with data sources.
  • View: Responsible for the UI and user interactions.
  • ViewModel: Acts as a bridge between the View and Model, providing data and handling UI logic.

Unit testing each layer in an MVVM architecture offers numerous benefits:

  • Early Bug Detection: Identify and fix issues before they propagate to other parts of the app.
  • Improved Code Quality: Write cleaner, more concise, and maintainable code.
  • Accelerated Development: Refactor code and add new features with confidence.
  • Enhanced Collaboration: Maintain consistent code quality across the team.

Setting Up the Environment

  1. Android Studio: Ensure you have the latest version installed.
  2. Testing Framework: Add the necessary testing framework to your app/build.gradle file:

    testImplementation 'junit:junit:4.13.2'
    androidTestImplementation 'androidx.test.ext:junit:1.1.5'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
  3. Testing Library: Consider using a testing library like Mockito or MockK to create mock objects for testing dependencies.

Unit Testing ViewModels

  1. Create a Test Class: Create a separate test class for each ViewModel you want to test.
  2. Set Up Test Dependencies: Use dependency injection frameworks like Dagger Hilt or Koin to inject dependencies into your ViewModel. For testing, use mock objects to simulate the behavior of these dependencies.
  3. Write Test Cases: Write comprehensive test cases covering various scenarios:
  • Input Validation: Test how the ViewModel handles invalid input.
  • Data Transformation: Test how the ViewModel transforms data from the Model.
  • UI Updates: Test how the ViewModel updates the UI through LiveData or StateFlow.
  • Error Handling: Test how the ViewModel handles errors and exceptions.

Example:

@RunWith(AndroidJUnit4::class)
class MyViewModelTest {

    @Test
    fun `should update the UI when data is fetched successfully`() {
        // ... (Arrange)
        val viewModel = MyViewModel(mockRepository)

        // ... (Act)
        viewModel.fetchData()

        // ... (Assert)
        viewModel.uiState.observeForever { uiState ->
            assertThat(uiState.isLoading).isFalse()
            assertThat(uiState.error).isNull()
            assertThat(uiState.data).isEqualTo(expectedData)
        }
    }
}

Unit Testing Repositories

  1. Create Test Classes: Create separate test classes for each Repository class.
  2. Set Up Test Dependencies: Use dependency injection to inject dependencies into your Repository. For testing, use mock objects to simulate the behavior of data sources like databases or network APIs.
  3. Write Test Cases: Write test cases to cover:
  • Data Fetching: Test how the Repository fetches data from remote or local sources.
  • Data Storage: Test how the Repository stores and retrieves data.
  • Data Manipulation: Test how the Repository processes and transforms data.
  • Error Handling: Test how the Repository handles errors and exceptions.

Example:

@RunWith(AndroidJUnit4::class)
class MyRepositoryTest {

    @Test
    fun `should fetch data from remote source successfully`() {
        // ... (Arrange)
        val mockApi = mock(MyApi::class.java)
        val repository = MyRepository(mockApi)

        // ... (Act)
        repository.fetchData()

        // ... (Assert)
        verify(mockApi).fetchData()
    }
}

Implementing SonarQube

SonarQube is a powerful tool for code quality and security analysis. Here’s a detailed guide on how to integrate SonarQube with your Android project:

  1. Set Up SonarQube Server:
  • Install SonarQube Server: Download and install the SonarQube server on your machine or a server.
  • Configure SonarQube: Configure the server with database settings, user authentication, and other necessary parameters.
  • Start SonarQube Server: Start the SonarQube server.
  1. Configure SonarQube Scanner:
  • Install SonarQube Scanner: Download and install the SonarQube Scanner.
  • Configure Scanner Properties: Create a sonar-scanner.properties file in your project’s root directory and configure the following properties:

    sonar.host.url=http://localhost:9000
    sonar.login=your_sonar_login
    sonar.password=your_sonar_password
    sonar.projectKey=my-android-project
    sonar.projectName=My Android Project
    sonar.sources=src/main/java
    sonar.java.binaries=build/intermediates/javac/release/classes
  1. Integrate SonarQube with Your Build Process:
  • Gradle: Add the SonarQube Gradle plugin to your build.gradle file:

    plugins {
        id 'org.sonarsource.scanner-gradle' version '3.3'
    }

    Configure the plugin with your SonarQube server URL and authentication token.

  • Maven: Add the SonarQube Maven plugin to your pom.xml file. Configure the plugin with your SonarQube server URL and authentication token.
  1. Run SonarQube Analysis:
  • Execute the SonarQube analysis using the SonarQube Scanner. This can be done manually or integrated into your CI/CD pipeline.
  1. Analyze the Results:
  • Once the analysis is complete, you can view the results on the SonarQube dashboard. The dashboard provides insights into code quality, security vulnerabilities, and potential improvements.

Implementing Test Coverage with Bitrise

Test coverage measures the percentage of your code that is covered by tests. It’s a crucial metric to assess the quality of your test suite. Here’s how to measure test coverage with Bitrise:

  1. Configure Code Coverage Tool: Add a code coverage tool like JaCoCo to your project. Configure it to generate coverage reports in a suitable format (e.g., XML).
  2. Add Code Coverage Step to Bitrise Workflow: Add a step to your Bitrise Workflow to generate the code coverage report. This step should execute your tests and generate the report.
  3. Upload Coverage Report to SonarQube: Add a step to upload the generated code coverage report to SonarQube. This will allow SonarQube to analyze the report and display the coverage metrics.

Best Practices for Unit Testing

  • Write Clear and Concise Tests: Use descriptive names for test methods and variables.
  • Test Edge Cases: Consider testing scenarios with invalid input, empty data, or network errors.
  • Use a Testing Framework: A testing framework like JUnit provides a structured way to write and run tests.
  • Leverage Mocking: Use mocking frameworks like Mockito or MockK to isolate units of code and control their behavior.
  • Automate Testing: Integrate unit tests into your CI/CD pipeline to ensure code quality.
  • Review and Refactor Tests: Regularly review and refactor your tests to keep them up-to-date and maintainable.

By following these guidelines and incorporating unit testing into your development process, you can significantly improve the quality and reliability of your Android apps.

]]>
https://blogs.perficient.com/2024/11/26/unit-testing-in-android-apps-a-deep-dive-into-mvvm/feed/ 0 372567
Set Your API Performance on Fire With BlazeMeter https://blogs.perficient.com/2024/05/20/set-your-api-performance-on-fire-with-blazemeter/ https://blogs.perficient.com/2024/05/20/set-your-api-performance-on-fire-with-blazemeter/#respond Mon, 20 May 2024 15:45:43 +0000 https://blogs.perficient.com/?p=358370

BlazeMeter, continuous testing platform,  is a perfect solution for your performance needs. BlazeMeter is an open-source tool that supports Web, Mobile and API implementations. You can perform large scale load and performance testing with the ability to tweak parameters to suit your needs.

We will learn step by step process on using BlazeMeter for API testing.

Register for BlazeMeter

Enter your information on the BlazeMeter site to register and get started

Configure Your First Scenario

The first time you login, you will be taken to default view of BlazeMeter with default workspace and project. Let us start configuring a new scenario.

Create a New Project

  1. Select Projects -> Create new project
  2. Name project
  3. Select Create Test
  4. Select Performance Test
  5. Now you are taken to configuration tab

 

Update Your Scenario

  1. The left section here has your test specifications
  2. Tap on Edit link and start updating your project name, let it be “FirstLoadTest”
  3. You can define scenario and test data in Scenario Definition section
  4. For this Demo we will configure API endPoint, tap on Enter URL/API calls (see picture below)
  5. In Scenario Definition enter “https://api.demoblaze.com/entries“. So we are load testing this endpoint with GET call
  6. Lets Name this scenario “DemoWithoutParameters”
  7. Tap on three dots next to Scenario definition and duplicate the scenario
  8. Name this as “DemoWithParameters”

Test Specifications

Create TestData

Create New Csvfile

  1. Next to Scenario Definition we have TestData section, tap on it
  2. You can choose from options available, for this demo we will go with “Create New Data Entity”
  3. Lets name it “DemoTestData” and Add it
  4. Tap on + icon next to entity created for parameterization options
  5. In this example we will select New CSV File
  6. You will be taken to a data table. Rename “variableName1” to “Parameter1” and “variableName2” to “Parameter2″(our variable names are “Parameter1” and “Parameter”)
  7. Enter values as “Value1” and “Value2” and Save
  8. Configure these parameters in Query Parameters section (See picture below)
  9. Now we have successfully completed building a scenario with two endpoints, you can configure one or more endpoints in one scenario

Scenariodefinition

Configure Your First Test Run

  1. Scroll down the scenario definition window to see Load Configuration section
  2. Enter Total Users, Duration, Ramp up Time. For now we can just test with 2 users, Duration: 1minute, RampupTime: 0
  3. Once you update these details observe the graphical representation of how your Load Test is going to be in the graph displayed in this section.
  4. We can also limit Requests Per Second(RPS) by enabling the toggle button for “Limit RPS” and select requests you need to limit per second
  5. We can also change number of users at run time, but this is available with only Enterprise Plan.
  6. Lets configure LoadDistribution now in “Load Distribution” section which is right below the “Load Configuration” section
  7. Select the location from where you need the requests to trigger.
  8. We can select multiple locations and distribute load across different locations, but again this feature is available with only enterprise plan
  9. For now, lets proceed by selecting one location

Load Configuration

Failure Criteria

  1. Failure Criteria is the best approach to immediately know your LoadTest Results
  2. Do you have your failure criteria defined? If yes, you can configure that in this section. This is optional, you can skip if you don’t have failure criteria defined.
  3. You can configure multiple failure criteria as well
  4. Enable “1-min slide window eval” for evaluating your loudest prior to execution
  5. Select “Stop Test?” checkbox if you want to stop the execution in case of failure
  6. Select “Ignore failure criteria during rampup” to ignore the failures during ramp-ups
  7. You can add one or more failure criteria and select this option uniquely for each criteria
  8. Select the option “Enable 1-min slide window eval for all” on top right of this section to enable for all provided failure criteria

Failure Criteria

Test Your Scenario

  1. Run your scenario by clicking on “RunTest”
  2. Wait for launch Test window to load completely
  3. Now click on “Launch Servers” button
  4. Click on “Abort Test” to abort your execution any time
  5. Observe your execution go through different stages (Pending, Booting, Downloading and Ready)
  6. Once it reaches Ready you can see your execution progress
  7. Once the execution is done you can view the summary with status as passed/failed

Blaze Executionstatus

Analyze Your LoadTest Results

  1. The important part of performance test is to analyze your KPIs
  2. You can see different KPIs in test results summary
  3. To understand more navigate to “Timeline Report” section, bottom left you can see “KPI Panel”,this panel contains different KPIS.These KPIs can be analyzed as required
  4. By default it provides generalized view, you can select single endpoint to analyze KPIs for one particular endpoint

Blazemeter Analyze Results

Schedule Your Load Tests

  1. BlazeMeter is continuous Integration tool, you can schedule your executions and view results when required
  2. Select your test from Tests Menu on top
  3. On to left of project description window you can find SCHEDULE section
  4. Tap on Add button next to it Schedule to see schedule window
  5. Configure the scheduler with required timings and Save the scheduler
  6. The new scheduler will be added to your project
  7. Delete it by tapping on Delete icon
  8. You can add multiple schedulers
  9. Toggle on/off to activate/deactivate the schedulers

Schedule Section

BlazeMeter Pros/Cons

ProsCons
Open sourceRequires a license for additional features and support
Provides Scriptless performance testingTest results analysis requires expertise
Integration with Selenium, JMeter, Gatling, LocustNeed to integrate with Selenium/JMeter to test functional scenarios
User-friendly UI
Report Monitoring from any geographic location
Integrates with CI/CD pipelines

If you are looking for a tool that services your performance needs, BlazeMeter is your best option. You can generate scripts with its scriptless UI, simulate loads and run your tests. You can also simulate the spinning up servers, script runs and results generated within seconds.

For more information about Perficient’s Mobile Solutions expertise, subscribe to our blog or contact our Mobile Solutions team today!

]]>
https://blogs.perficient.com/2024/05/20/set-your-api-performance-on-fire-with-blazemeter/feed/ 0 358370
Level Up Your Map with the ArcGIS SDK https://blogs.perficient.com/2024/05/09/level-up-your-map-with-the-arcgis-sdk/ https://blogs.perficient.com/2024/05/09/level-up-your-map-with-the-arcgis-sdk/#respond Thu, 09 May 2024 16:12:08 +0000 https://blogs.perficient.com/?p=356410

In today’s tech-driven world, the ability to visualize data spatially has been vital for various industries. Enter ArcGIS, a Geographic Information System (GIS) developed by ESRI, which is here to help us solve our client’s needs. Let’s chart our way into the world of ArcGIS and how it empowers businesses to harness the full capabilities of today’s mapping software.

Overview

At its core, ArcGIS is a comprehensive mapping solution that enables you to deliver a high quality experience for your users. It integrates various geographic data sets, allows users to overlay layers, analyze spatial relationships and extract meaningful insights. The user-friendly features and wide array of capabilities differentiates ArcGIS from competitors.

Standard Features

ArcGIS offers a plethora of map features designed to level up your user’s experience. Basic features such as customizable basemap tiles, display the user’s location in real-time and intuitive pan and zoom functions all makes map navigation a smooth and familiar experience.

However, the true power of ArcGIS lies in its ability to visualize and interact with objects on a map. Custom-styled map markers with the same look and feel of pre-existing symbols, enables users to identify and track objects just as they’re used to seeing them. And if you have many objects in close proximity to one another? Group them together with “clusters” that can break apart or regroup at specific zoom levels.

Advanced Features

By providing methods to display object details or toggle visibility based on predefined groups, ArcGIS gives businesses the power to streamline asset management. And that just scratches the surface of the advanced features available!

With ArcGIS, you can draw on the map to indicate an area, or even let your users draw on the map themselves. You can apply a “highlight” styling on visible objects that meet a criteria. You can search for objects with a multitude of filters, such as object type, any custom attributes (defined and set by your organization’s data management team), or even search for objects within a defined geographical boundary.

The limit of its applications is your imagination.

Offline Maps

But what happens when you’re off the grid? Won’t we lose all of these convenient features? Fear not, as ArcGIS enables continued productivity even in offline environments.

By downloading map sections for offline use, users can still access critical data and functionalities without internet connectivity, a feature especially useful for your on-the-go users.

If storage space is a concern, you can decide which data points for objects are downloaded. So if your users just need to see the symbols on the map, you can omit the attributes data to cut down on payload sizes.

In conclusion, ArcGIS stands as one of the leaders in mapping technology, empowering businesses to unlock new opportunities. From basic map features to advanced asset management capabilities, ArcGIS is more than just a mapping solution—it’s a gateway to spatial intelligence. So, embrace the power of ArcGIS and chart your path to success in the digital age!

For more information about Perficient’s Mobile Solutions expertise, subscribe to our blog or contact our Mobile Solutions team today!

]]>
https://blogs.perficient.com/2024/05/09/level-up-your-map-with-the-arcgis-sdk/feed/ 0 356410
Make Your Flutter Apps Soar with Pigeon Platform Channels https://blogs.perficient.com/2024/03/21/make-your-flutter-apps-soar-with-pigeon-platform-channels/ https://blogs.perficient.com/2024/03/21/make-your-flutter-apps-soar-with-pigeon-platform-channels/#respond Thu, 21 Mar 2024 18:08:29 +0000 https://blogs.perficient.com/?p=352054

Flutter is great framework for cross platform development. It allows you to make pixel perfect apps that are generated into native code, but what happens if you need to use existing code in iOS or Android directly? For situations like these, Flutter allows you to use platform channels.

Platform channels give you access to platform-specific APIs in a language that works directly with those APIs. Platform channels are available for Kotlin or Java on Android, Swift or Objective-C on iOS and macOS, C++ on Windows and C on Linux.

More information can be found on this here https://docs.flutter.dev/platform-integration/platform-channels

The platform APIs provided by Flutter work as intended, but the whole process is a bit cumbersome to set up. Pigeon allows us to use type safety and code generation to make this process a whole lot simpler.

Create a Pigeon Plugin

We will go ahead and create a simple example api.

Let’s start by creating a new plugin called pigeon_example

flutter create --org com.example --template=plugin --platforms=android,ios,linux,macos,windows -i swift pigeon_example
flutter pub add pigeon
flutter pub get

Platform Channel Types in Swift

Below is a list of supported types in Dart and their swift equivalents. We will use some of the most common types in our example

Dart TypesSwift Types
nullnil
boolNSNumber(value: Bool)
intNSNumber(value: Int32)
int, if 32 bits not enoughNSNumber(value: Int)
doubleNSNumber(value: Double)
StringString
Uint8ListFlutterStandardTypedData(bytes: Data)
Int32ListFlutterStandardTypedData(int32: Data)
Int64ListFlutterStandardTypedData(int64: Data)
Float32ListFlutterStandardTypedData(float32: Data)
Float64ListFlutterStandardTypedData(float64: Data)
ListArray
MapDictionary

Define Our API

In order to let Pigeon know what methods we’re going to be exposing we define our API in an abstract Dart class with the @HostApi() decorator, and its methods

Let’s define our Pigeon Example API in a new directory named pigeons.

import 'package:pigeon/pigeon.dart';

@HostApi()

abstract class ExampleApi {
bool getBool();
String getString();
func toggleValue();
}

Generate Pigeon Platform Code

Now we can let the Pigeon package do it’s magic and we can generate some code

dart run pigeon \
--input pigeons/example_api.dart \
--dart_out lib/example_api.dart \
--experimental_swift_out ios/Classes/ExampleApi.swift \
--kotlin_out ./android/app/src/main/kotlin/com/example/ExampleApi.kt \
--java_package "io.flutter.plugins"

Be sure that the paths to all of the files are correct or the next steps won’t work. Generate the code with the output for the platforms needed. This is example is going to focus on using Swift.

Add Method Implementation to the Runner

Next we need to write our native implementation of our methods. When doing this we need to add our files to the runner in Xcode to ensure that they run properly.

class ExampleApiImpl : ExampleApi{
var value = true;

func getBool(){
return value;
}
func toggleValue(){
    value = !value
  }
func getString(){
return "THIS IS AN EXAMPLE";
}

}

Add Pigeon Platform Channel to AppDelegate

You will also need to add this code in your AppDelegate.swift file

@UIApplicationMain

@objc class AppDelegate: FlutterAppDelegate {

override func application(

_ application: UIApplication,

didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?

) -> Bool {

GeneratedPluginRegistrant.register(with: self)

let exampleApi = ExampleApiImpl()

let controller : FlutterViewController = window?.rootViewController as! FlutterViewController

ExampleApiSetup.setUp(binaryMessenger: controller.binaryMessenger, api: exampleApi)




return super.application(application, didFinishLaunchingWithOptions: launchOptions)

}

}

 

Now you should be able to use your API in Dart code.

 

import 'package:flutter/material.dart';
import 'package:pigeon_example/example_api.dart';
import 'dart:async';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatefulWidget {
  const MyApp({super.key});

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  final exampleApi = ExampleApi();
  bool value = false;
  @override
  void initState() {
    super.initState();
    initPlatformState();
  }

  // Platform messages are asynchronous, so we initialize in an async method.
  Future<void> initPlatformState() async {
    // Platform messages may fail, so we use a try/catch PlatformException.
    // We also handle the message potentially returning null.
    // If the widget was removed from the tree while the asynchronous platform
    // message was in flight, we want to discard the reply rather than calling
    // setState to update our non-existent appearance.
    if (!mounted) return;
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        home: Scaffold(
            appBar: AppBar(
              title: const Text('Plugin example app'),
            ),
            body:
                Column(mainAxisAlignment: MainAxisAlignment.center, children: [
              DefaultTextStyle(
                  style: Theme.of(context).textTheme.displayMedium!,
                  textAlign: TextAlign.center,
                  child: FutureBuilder<String>(
                    future: exampleApi
                        .getString(), // a previously-obtained Future<String> or null
                    builder:
                        (BuildContext context, AsyncSnapshot<String> snapshot) {
                      List<Widget> children = [];
                      if (snapshot.data!.isNotEmpty) {
                        children = <Widget>[
                          Text(snapshot.data ?? ''),
                        ];
                      }
                      return Center(
                        child: Column(
                          mainAxisAlignment: MainAxisAlignment.center,
                          children: children,
                        ),
                      );
                    },
                  )),
              Center(
                child: ElevatedButton(
                  child: const Text('Toggle Value'),
                  onPressed: () async {
                    await exampleApi.toggleValue();
                    var val = await exampleApi.getBool();
                    setState(() {
                      value = val;
                    });
                  },
                ),
              ),
              DefaultTextStyle(
                  style: Theme.of(context).textTheme.displayMedium!,
                  textAlign: TextAlign.center,
                  child: FutureBuilder<bool>(
                    future: exampleApi
                        .getBool(), // a previously-obtained Future<String> or null
                    builder:
                        (BuildContext context, AsyncSnapshot<bool> snapshot) {
                      List<Widget> children;
                      if (snapshot.data == true) {
                        children = <Widget>[
                          const Icon(
                            Icons.check_circle_outline,
                            color: Colors.green,
                            size: 60,
                          ),
                          Padding(
                            padding: const EdgeInsets.only(top: 16),
                            child: Text('Result: ${snapshot.data}'),
                          ),
                        ];
                      } else if (snapshot.data == false) {
                        children = <Widget>[
                          const Icon(
                            Icons.error_outline,
                            color: Colors.red,
                            size: 60,
                          ),
                          Padding(
                            padding: const EdgeInsets.only(top: 16),
                            child: Text('Result: ${snapshot.data}'),
                          ),
                        ];
                      } else {
                        children = const <Widget>[
                          SizedBox(
                            width: 60,
                            height: 60,
                            child: CircularProgressIndicator(),
                          ),
                          Padding(
                            padding: EdgeInsets.only(top: 16),
                            child: Text('Awaiting result...'),
                          ),
                        ];
                      }
                      return Center(
                        child: Column(
                          mainAxisAlignment: MainAxisAlignment.center,
                          children: children,
                        ),
                      );
                    },
                  ))
            ])));
  }
}

 

Now we can see the values from out example API in our Flutter UI. Button toggles will change our boolean value.

Simulator Screenshot Iphone 12 2024 03 21 At 12.56.11 Simulator Screenshot Iphone 12 2024 03 21 At 12.56.08

This same pattern can be used for any type of data supported by Pigeon.

Pigeon simplifies the process of creating platform channels. It also speeds up the process when multiple channels are needed. This becomes very valuable when you need a package that doesn’t have an implementation in Flutter. It’s a bit tricky to set up the first time, but once your scripts are written, modifying existing channels and creating new ones is a breeze.

For more information about Perficient’s Mobile Solutions expertise, subscribe to our blog or contact our Mobile Solutions team today!

 

]]>
https://blogs.perficient.com/2024/03/21/make-your-flutter-apps-soar-with-pigeon-platform-channels/feed/ 0 352054
Parameterize Your Automated QA Test Scenarios With Cucumber https://blogs.perficient.com/2024/02/27/parameterize-your-automated-qa-test-scenarios-with-cucumber/ https://blogs.perficient.com/2024/02/27/parameterize-your-automated-qa-test-scenarios-with-cucumber/#comments Tue, 27 Feb 2024 16:21:29 +0000 https://blogs.perficient.com/?p=355275

Creation of automated QA test scripts in Cucumber provides a low barrier of entry for your QA team. What is Cucumber? Cucumber is Behavior-Driven Development tool that uses Gherkin for scenario syntax. Gherkin is a simple, non-technical language designed for easy maintenance and readability. Gherkin can easily integrate with open-source tools like Selenium, Appium for QA Automation.

We can extend Gherkin syntax in Cucumber test scripts even further with parameters. Parametrization provides the ability to run your scenarios using different test data, write less code and reuse it in multiple locations. In this article, we will learn how to use parameters to create robust test scenarios.

Before digging deep into parameterization, let’s learn about a few keywords!

Useful Keywords:

Scenario: Group of steps that contains user actions and validations.

Test Step: Represents a single user action or validation defined in simple language and starts with keywords like Given, When, Then, And and But.

Step Definition: A method linked to each test step in a scenario. One step definition also can be linked to multiple test steps by following parameterization techniques.

Scenario Outline:  Keyword used for scenarios that contains parameters with values defined. Use Scenario Outline to enable parameters instead of the keyword Scenario. Parameters are defined as variables inside <>. The variables are defined via the Examples keyword.

Examples: Keyword to define variables for a Scenario Outline. I.E. Login credentials for test accounts.

Parameterization Scenarios:

Scenario 1:

Scenario: Verify user can login to Login Page1

Given I am in LoginPage1

When I enter username and password

Then I verify that user is logged into HomePage

And I verify that “home” tab is displayed

And I verify the page title

Scenario 2:

Scenario: Verify user can login to LoginPage2

Given I am in LoginPage2

When I enter username and password

Then I verify that user is logged into HomePage

And I verify that “user” tab is displayed

And I verify the page title and save the title

 

Parameterize with Pre-defined Values

Parameters that can allow only strongly typed values are considered as pre-defined values.

Let’s look into Given statements from above example – Scenario 1: “I am in LoginPage1” and Scenario 2: “I am in LoginPage2.” The steps are same except for the LoginPage1 and LoginPage2 values. We’re going to create a single step definition for both steps.

 

@Given(“^I am in (LoginPage1|LoginPage2)$”)

Public void iAmInLoginPage(String parameter1){

//code here

}

 

 

Note: The parametrized step definitions will start with ^ and ends with $

 

Parameterize with Undefined Values

Parameters with undefined values are variables that can have different input values.

We need to test the above scenarios with different login credentials, which can change often. This can be achieved by updating the test data in the scenario, not the entire script linked to the scenario. The test step “When I enter username and password” is the perfect candidate for our use case. Let’s use the Scenario Outline keyword and pass the parametrized values as Examples. We’ll use <> to pass the username and password to our When clause.

 

Scenario Outlines:

Scenario 1:

Scenario Outline: Verify user is able to login to LoginPage1

Given I am in LoginPage1

When I enter <username> and <password>

Then I verify that user is logged into HomePage1

And I verify that “home” tab is displayed

And I verify the page title and save the title

Examples:

|username|password|

|user1|password1|

|user2|password2|

Note: The above scenario will be executed with iterations for user1 and user2.

The step definitions should use regular expressions to pass values to the scenario as shown below.

 

@When(“^I enter (.*) and (.*)$”)

Public void iEnterUserNameAndPassword(String username,String password){

//code here

}

 

Parameterize with Fixed/Default Values:

Default values are parameters defined in test step of the scenario. We are not making use of parameter names, we are directly passing values from test steps instead.

Let’s investigate the test step Scenario 1: “And I verify that “home” tab is displayed” and Scenario 2: “And I verify that “user” tab is displayed.” Both steps are the same except for the values home and user. Even though they aren’t parameter names, they are values used directly in our test steps. However, we can still create single step definition and link it to both test steps.

 

@And(“I verify that {String} tab is displayed”)

Public void iVerifyGivenTanIsDisplayed(String tabName){

//code here

}

 

Parameterize Null Values:

There are times when a particular parameter can or cannot have a value within our scenarios. This is where we would make use of Null Value parameters.

Let’s take last steps “And I verify the page title” and “And I verify the page title and save the title.” We can create a parameter to accept “ and save the title” or null. This can be achieved with the regular expression character ? and ensure the parameter has a leading space.

@Then(“^I verify the page title( and save the title)?$”)

Public void iVerifyPageTitle(String saveTitle){

//codehere

If(saveTitle != null){

//code here

}

}

 

Test scenario parameters enable reuse for test steps across different scenarios. Your code will be more readable and portable as a result. Parameterization decreases our script efforts by linking step definitions to multiple test steps. Adoption of parameters improves code quality which will allow for less errors, easy updates and better overall code.

You will love the code efficiencies that parameters provide! Why not give them a try.

For more information about Perficient’s Mobile Solutions expertise, subscribe to our blog or contact our Mobile Solutions team today!

 

 

]]>
https://blogs.perficient.com/2024/02/27/parameterize-your-automated-qa-test-scenarios-with-cucumber/feed/ 1 355275
Generative AI Revolution: A Comparative Analysis https://blogs.perficient.com/2024/01/19/generative-ai-revolution-a-comparative-analysis/ https://blogs.perficient.com/2024/01/19/generative-ai-revolution-a-comparative-analysis/#respond Fri, 19 Jan 2024 16:21:50 +0000 https://blogs.perficient.com/?p=353976

In the world of Generative Artificial Intelligence (AI), a new era of large language models has emerged with the remarkable capabilities. ChatGPT, Gemini, Bard and Copilot have made an impact in the way we interact with mobile device and web technologies. We will perform a comparative analysis to highlight the capabilities of each tool.

 

ChatGPTGeminiBardCopilot
Training DataWebWebWebWeb
Accuracy85%85%70%80%
Recall85%95%75%82%
Precision89%90%75%90%
F1 Score91%92%75%84%
MultilingualYesYesYesYes
InputsGPT-3.5: Text Only
GPT-4.0: Text and Images
Text, Images and Google DriveText and ImagesText and Images
Real Time DataGPT-3.5: No
GPT-4.0: Yes
YesYesYes
Mobile SDKhttps://github.com/skydoves/chatgpt-androidAPI Onlyhttps://www.gemini.com/mobileAPI Only
CostGPT-3.5
GPT-4.0
Gemini Pro
Gemini Pro Vision
UndisclosedUndisclosed

Calculation Metrics:

TP – True Positive

FP – False Positive

TN – True Negative

FN – False Negative

Accuracy = (TP +TN) / (TP + FP + TN + FN)

Recall = TP / (TP + FN)

Precision = TP / (TP + FP)

F1 Score = 2 * (Precision * Recall) / (Precision + Recall)

 

Our sample data set consists of 100 queries against Gemini AI. The above formula applied calculates the following scores:

Accuracy: (85 + 0) /100 = 85%

Recall: 85/ (85 + 5) = 94.44%

Precision: 85/ (85 + 10) = 89.47%

F1-Score: 2 * (0.894 * 0.944) / (0.894 + 0.944) = 91.8%

 

Recommended AI Tool:

I recommend Gemini based on its accuracy and consistency.  The ease of integration to the iOS and Android platforms and performance stands out amongst it’s competitors. We will illustrate how easy it is to integration Gemini with 10 easy steps.

Let’s Integrate Gemini into an Android Application!

  1. Download the Android Studio preview release Canary build (Jelly Fish| 2023.3.1).
  2. Create a new project: File -> New -> New Project
  3. Select the Phone and Tablet
    1. Under New Project -> Generate API Starter
    2. Click Next to Proceed
  4. Fill all the necessary details
    1. Enter the Project Name: My Application (or whatever you want to name your project)
    2. Enter the Package Name: (com.companyname.myapplication).
    3. Select the Location to save the project
    4. Select the Minimum SDK version: as API 26 (“Oreo”;Android 8.0)
    5. Select the Build Configuration Language as Kotlin DSL(build.gradle.kts)
    6. Click Finish to proceed 
  5. Create a starter app using the Gemini API
  6. To generate the API. Go to Gemini Studio.
  7. Click Get API Key -> click Create API Key in New Project or Create API Key in Existing Project in the Google AI studio
  8. Select the API key from the Prompt and paste in the Android Studio.
  9. Click Finish to proceed.
  10. Click the Run option in the Android Studio.

And you’re up and running with Generative AI in your Android app!

I typed in “Write a hello world code in java” and Gemini responded with code snippet. You can try out various queries to personalize your newly integrated Generative AI application to your needs.

Screenshot 2024 01 17 At 10.05.06 Pm

Alternatively, you can just Download the Sample app from the GitHub and add the API key to the local.properties to run the app.

It’s essential to recognize the remarkable capabilities of Generative AI tools on the market. Comparison of various AI metrics and architecture can give insight into performance, limitations and suitability for desired tasks. As the AI landscape continues to grow and evolve, we can anticipate even more groundbreaking innovations from AI tools. These innovations will disrupt and transform industries even further as time goes on.

For more information about Perficient’s Mobile Solutions expertise, subscribe to our blog or contact our Mobile Solutions team today!

]]>
https://blogs.perficient.com/2024/01/19/generative-ai-revolution-a-comparative-analysis/feed/ 0 353976
White Label Your Mobile Apps with Azure https://blogs.perficient.com/2023/12/21/white-label-your-mobile-apps-with-azure/ https://blogs.perficient.com/2023/12/21/white-label-your-mobile-apps-with-azure/#respond Thu, 21 Dec 2023 15:44:28 +0000 https://blogs.perficient.com/?p=338661

Enterprises and organizations that manage products with overlapping feature sets often confront a unique challenge. Their core dilemma involves creating multiple branded mobile applications that share a common codebase while enabling each app to provide a distinct user experience with minimal development overhead. As a leader in custom mobile solutions, Perficient excels in white labeling mobile applications using the power and flexibility of Azure DevOps.

Tackling the White Label Challenge

Consider a scenario where your application has gained popularity, and multiple clients desire a version that reflects their own brand identity. They want their logos, color schemes, and occasionally distinct features, yet they expect the underlying functionality to be consistent. How do you meet these demands without spawning a myriad of codebases that are a nightmare to maintain? This post outlines a strategy and best practices for white labeling applications with Azure DevOps to meet this challenge head-on.

Developing a Strategy for White Label Success

White labeling transcends merely changing logos and color palettes; it requires strategic planning and an architectural approach that incorporates flexibility.

1. Application Theming

White labeling starts with theming. Brands are recognizable through their colors, icons, and fonts, making these elements pivotal in your design. Begin by conducting a thorough audit of your current style elements. Organize these elements into variables and store them centrally, setting the stage for smooth thematic transitions.

2. Establishing Your Default Configuration

Choosing a ‘default’ configuration is crucial. It sets the baseline for development and validation. The default can reflect one of your existing branded applications and acts as a unified starting point for addressing issues, whether related to implementation or theming.

3. Embracing Remote/Cloud Configurations

Tools like the Azure App Configuration SDK or Firebase Remote Configuration allow you to modify app settings without altering the code directly. Azure’s Pipeline Library also helps manage build-time settings, supporting flexible brand-specific configurations.

Using remote configurations decouples operational aspects from app logic. This approach not only supports white labeling but also streamlines the development and customization cycle.

Note: You can add your Brand from the step 2. Adding Your “Brand” Configuration to Your Build into your build artifacts, and reference the correct values in your remote configurations for your brand.

Coordinating White Labeled Mobile Apps with Azure Pipelines

With your application ready for theming and remote configuration, use Azure Pipelines to automate the build and release of your branded app artifacts. The structure of your build stages and jobs will depend on your particular needs. Here’s a pattern you can follow to organize jobs and stages for clarity and parallelization:

1. Setting Up Your Build Stage by Platforms

Organize your pipeline by platform, not brand, to reduce duplication and simplify the build process. Start with stages for iOS, Android, and other target platforms, ensuring these build successfully with your default configuration before moving to parallel build jobs.

Run unit tests side by side with this stage to catch issues sooner.

2. Adding Your “Brand” Configuration to Your Build

Keep a master list of your brands to spawn related build jobs. This could be part of a YAML template or a file in your repository. Pass the brand value to child jobs with an input variable in your YAML template to make sure the right brand configuration is used across the pipeline.

Here’s an example of triggering Android build jobs for different brands using YAML loops:

stages:
    - stage: Build
      jobs:
          - job: BuildAndroid
            strategy:
                matrix:
                    BrandA:
                        BrandName: 'BrandA'
                    BrandB:
                        BrandName: 'BrandB'
            steps:
                - template: templates/build-android.yml
                  parameters:
                      brandName: $(BrandName)

3. Creating a YAML Job to “Re-Brand” the Default Configuration

Replace static files specific to each brand using path-based scripts. Swap out the default logo at src/img/logo.png with the brand-specific logo at src/Configurations/Foo/img/logo.png during the build process for every brand apart from the default.

An example YAML snippet for this step would be:

jobs:
    - job: RebrandAssets
      displayName: 'Rebrand Assets'
      pool:
          vmImage: 'ubuntu-latest'
      steps:
          - script: |
                cp -R src/Configurations/$(BrandName)/img/logo.png src/img/logo.png
            displayName: 'Replacing the logo with a brand-specific one'

4. Publishing Your Branded Artifacts for Distribution

Once the pipeline jobs for each brand are complete, publish the artifacts to Azure Artifacts, app stores, or other channels. Ensure this process is repeatable for any configured brand to lessen the complexity of managing multiple releases.

In Azure, decide whether to categorize your published artifacts by platform or brand based on what suits your team better. Regardless of choice, stay consistent. Here’s how you might use YAML to publish artifacts:

- stage: Publish
  jobs:
      - job: PublishArtifacts
        pool:
            vmImage: 'ubuntu-latest'
        steps:
            - task: PublishBuildArtifacts@1
              inputs:
                  PathtoPublish: '$(Build.ArtifactStagingDirectory)'
                  ArtifactName: 'drop-$(BrandName)'
                  publishLocation: 'Container'

By implementing these steps and harnessing Azure Pipelines, you can skillfully manage and disseminate white-labeled mobile applications from a single codebase, making sure each brand maintains its identity while upholding a high standard of quality and consistency.

For more information about Perficient’s Mobile Solutions expertise, subscribe to our blog or contact our Mobile Solutions team today!

]]>
https://blogs.perficient.com/2023/12/21/white-label-your-mobile-apps-with-azure/feed/ 0 338661
Forget Okta? Well, It Might Not Forget You : Solving Remember Device Issues https://blogs.perficient.com/2023/12/11/forget-okta-well-it-might-not-forget-you-solving-remember-device-issues/ https://blogs.perficient.com/2023/12/11/forget-okta-well-it-might-not-forget-you-solving-remember-device-issues/#respond Mon, 11 Dec 2023 14:21:08 +0000 https://blogs.perficient.com/?p=335336

Why Okta?

Who can forget Okta? It is basically a household name at many enterprise companies. Even if you don’t know about Okta, you probably recognize the name. Okta is a cloud-based identity and access management platform that provides secure user authentication and authorization services. It enables organizations to manage and control access to various software applications, systems, and data resources across multiple devices and networks.

In my opinion, the best reasons to use Okta are for multi-factor authentication (MFA) and single sign-on (SSO) functionality. It adds an additional layer of security, enables users to access multiple applications and services with a single set of credentials, eliminating the need for them to remember multiple usernames and passwords.

MFA is extremely important to secure the login flow because it adds an extra layer of verification beyond just a password, which are stolen and leaked every day through phishing and data breaches. We all love a security sidekick, but those extra authentication steps can feel exhaustive and over protective, especially if the login is happening on the same device with the same credentials.

This is where Remember Device comes in and allows users to conveniently access their accounts without the need for frequent MFA. Okta provides this functionality and has several recommended ways to implement it.

Getting Technical

Okta’s documentation provides implementation insight for the Remember Device feature. They have their own implementation, but it may be beneficial to generate your own token for the devices your users access the application from.

To achieve this, Okta exposes the “deviceToken” property that can be included in the context object of a standard authentication request. This device token serves as a unique identifier for the user’s device. Okta makes it clear that a property override is considered a highly privileged action, requiring an administrator API token. The introduction of the context object in this manner ensures Okta remembers this device token regardless of how the user proceeds through the authentication process.

This is where a problem starts to show up. Even if the user doesn’t explicitly set the “rememberDevice” query parameter as true during the MFA process, the device token will live in Okta for that user for some long unknown amount of time. Okta’s recommended approach saves the device token EVERY SINGLE TIME. Even if a user selects Do Not Remember, anyone who saves or has access to that device token can use it to bypass MFA the next time that account is logged in. This can be especially tricky when multiple users may have accounts on the same device.

In the Real World:

  • 300 users login daily for 1 week
  • 300 x 7 = 2100 device tokens stored in Okta per week
  • Okta doesn’t provide any information on it’s threshold for storing these values
  • All prior tokens are still active and can bypass MFA

Luckily, as with most technical challenges, the company responsible has already discovered a way to solve the problem. Let’s take a look at Okta’s own UI widget they provide for login and MFA.

Okta Uses Cookies 

When authenticating through Okta’s widget, the same issue does not occur. If you take the device token generated on login, don’t select “remember device” and try to force that same token on a subsequent login, Okta treats it as a new device token. What is Okta’s widget doing that it’s documentation is hiding from us?

Okta uses the Device Token (DT) cookie as a secure token that is stored on a user’s device after successful authentication. It serves as a form of identification for the device and allows Okta to recognize and trust that device for future login attempts.

This cookie is used by their system to store during an MFA call if the rememberDevice query parameter is true. If that parameter is not true, then Okta will NOT save that token. When integrating with Okta through a custom backend service, if you really want to ensure that the device tokens you generate are not being maintained by Okta, then you will need to set the DT cookie value instead of the context object.

This is not a privileged action, it doesn’t save the device token. Okta even conveniently provides their own device token using that cookie if one is not passed in through the context object or the DT cookie on the initial authentication request.

What’s the Catch?

Unfortunately, this solution only works for Okta integration into a back-end service. Due to the updated Fetch spec, browsers block any JavaScript from accessing the “Set-Cookie” headers.

The only way to approach custom device token generation from a JavaScript front-end, is by using the context object and ensuring that each token generated is unique per user per device.

Conclusion

Okta still provides one of the strongest authentication systems in the tech industry and it will likely be around for a long time. If you want to have a more custom experience and generate your own tokens, keep in mind the issues that can arise from using Okta’s recommended approach. With a backend service, we can work around these issues and ensure we create only a single device token per user upon request. If your solution does not require customization, we recommend the Okta widget that already has the proper device token management built in.

This way Okta will only remember you the way you want them to.

For more information about Perficient’s Mobile Solutions expertise, subscribe to our blog or contact our Mobile Solutions team today!

]]>
https://blogs.perficient.com/2023/12/11/forget-okta-well-it-might-not-forget-you-solving-remember-device-issues/feed/ 0 335336
How Do I Choose the Best QA Test Case Repository for My Team? https://blogs.perficient.com/2023/11/30/how-do-i-choose-the-best-qa-test-case-repository-for-my-team/ https://blogs.perficient.com/2023/11/30/how-do-i-choose-the-best-qa-test-case-repository-for-my-team/#respond Thu, 30 Nov 2023 15:38:15 +0000 https://blogs.perficient.com/?p=349733

Considering the growing importance of mobile and web applications, choosing a QA Test Repository for your organization can be a daunting task with the options available on the market.  It can be difficult to determine where to start when evaluating tools and options. We can help jumpstart that process! Let’s dig into the benefits and drawbacks of four popular test case repositories: X-Ray, TestLink, TestRail and Zephyr. These tools play a vital role in QA test case creation, execution, and reporting regardless of team size. 

FeatureX-RayZephyrTestRailTestLink
Test Automation IntegrationSupports integration with various test automation frameworksAllows execution of test cases within the toolLimited integration with automation toolsDependent on manual execution
ReportingOffers advanced reporting capabilitiesOffers advanced reporting capabilitiesOffers comprehensive reportingLimited reporting compared to X-Ray/TestRail
Third Party IntegrationSupports JIRA integration onlySupports JIRA integration onlyHighly dependent on third party integrationsLimited vendor support due to being open sourced
User InterfaceSteep learning curve for new usersInitial setup can be difficultIntuitive and user friendlyA bit dated compared to others
Customization Extensive customization options to tailor case templates to specific needsComplex customization can create confusion or inefficienciesFlexible customization options for adapting to different testing methodologiesOffers simple customization options
Open SourceNoNoNoYes
Cost$$$$$$$$$

 

Recommendations 

For Large Teams: Zephyr or X-Ray

  • Key Features:
    • Customizable workflows
    • Keeps team members in sync
  • Reasoning:
    • Zephyr is recommended for large teams due to its customization features, ensuring alignment in workflows and promoting team collaboration.
    • X-ray is a suitable choice for larger teams already using Jira, offering seamless integration and a comprehensive testing solution.

For Teams of Any Size: TestRail

  • Key Features:
    • Simple user interface
    • Easy to learn
  • Reasoning:
    • TestRail is recommended for teams of any size due to its user-friendly interface, making it easy for both small and medium-sized teams to adopt.

For Small Teams: TestLink

  • Key Features:
    • Affordability
  • Reasoning:
    • TestLink is a budget-friendly option, making it well-suited for small and medium-sized teams with budget constraints.

 

In the realm of mobile testing where a wide range of devices and operating systems presents its set of challenges, having a strong and extensive collection of test cases is incredibly important. A structured and comprehensive test case repository forms the foundation of mobile testing strategies. It acts as a hub simplifying test case management promoting teamwork and cooperation, among team members and maintaining uniformity, in testing processes across mobile platforms.

For more information about Perficient’s Mobile Solutions expertise, subscribe to our blog or contact our Mobile Solutions team today!

]]>
https://blogs.perficient.com/2023/11/30/how-do-i-choose-the-best-qa-test-case-repository-for-my-team/feed/ 0 349733
VoiceOver Your TalkBack To Me: A Mobile Screen Reader Overview https://blogs.perficient.com/2023/11/16/voiceover-your-talkback-to-me-a-mobile-screen-reader-overview/ https://blogs.perficient.com/2023/11/16/voiceover-your-talkback-to-me-a-mobile-screen-reader-overview/#respond Thu, 16 Nov 2023 15:25:50 +0000 https://blogs.perficient.com/?p=349361

Accessibility testing on Websites and apps with automated accessibility scanner tools are a great help but they only get you so far.  Screen Readers will bridge the gap in your accessibility goals to reach your user base spanning various impairments.

Using Screen Readers

Text-to-Speech Screen Reader technology (generally referred to as simply “Screen Readers”) can be used to help people who are blind or otherwise visually impaired to use and enjoy websites and applications. Onscreen content and instructions can be read aloud by the screen reader software so that the user is able to read, navigate, and access the content and functionality available to sighted users.

Because most Screen Reader users don’t use a mouse, a variety of either keyboard commands (for desktop/laptop computers) or touch commands (for mobile handheld devices) are available to users. Here, we’ll focus on some of these commands for using mobile Screen Readers.

For developers, setting up your website or application for a good Screen Reader experience, using modern, valid HTML will get you most of the way there – but to optimize the experience your code will need to be configured.

Additional Code Elements Include:

  • Accessibility Identifiers: Mobile element identification id that allows for easy traversal of screen elements. Eliminates possible XPath errors and can allow be used for QA Automation
  • “alt” attributes for images: these attributes are applied to HTML tags, and are best used to provide concise descriptions of images on the screen
  • ARIA attributes:
    • “aria-label”: used to provide additional description, often used for interactive elements that don’t have an accessible name (e.g. icons)
    • “role”: used to describe elements that don’t natively exist in HTML, or elements that do exist but don’t yet have full browser support
    • Various state and property aria attributes (e.g. https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Techniques)
  • Headers (h1-h6): used properly, these form a sort of outline of the content of a page
  • Landmarks: a way to identify the various regions of a page (e.g. navigation, main content, etc.)

The two most widely used mobile screen readers are VoiceOver (iOS) and TalkBalk (Android). 

Users need to learn a set of touch gestures to use these mobile screen readers. The most basic gestures used are tapping to select items, and swiping right or left to respectively select the next or previous item to interact with on the webpage. As items are selected, their onscreen text and perhaps other information will be spoken by the screen reader voice.

Just as a sighted user can visually scan pages displayed on the screen to more quickly get to the info or functionality they want, screen reader users can navigate using these multi-finger gestures in a similar fashion. These users are not locked into going though all the content in sequence. There are additional gestures available to users to perform more complex interactions.

VoiceOver

VoiceOver provides the ability to traverse page elements with gestures, keystrokes, and Voice Recognition. It also supports braille displays – users can connect a Bluetooth® wireless braille display to read the output if they prefer that to the voice output.

VoiceOver offers a wider range of gestures beyond the basic swiping/tapping gestures. 

Additional VoiceOver Gestures:

  • Mute/Unmute VoiceOver: Three-finger double-tap
  • Use a standard gesture: Double-tap and hold your finger on the screen until you hear three rising tones, then make the gesture. When you lift your finger, VoiceOver gestures resume.
  • Open the Item Chooser: Two-finger triple tap
  • Rotor: Initiated by using an onscreen two-finger rotation – among other multi-finger gestures to change page
  • “Magic Tap”: Two-finger double tap; this is the only gesture that can be programmed to perform different actions in different apps

Full gesture list for VoiceOver: https://support.apple.com/guide/iphone/learn-voiceover-gestures-iph3e2e2281/ios

TalkBack

TalkBack also provides eyes-free navigation and control of applications, allowing users to use gestures and voice commands. It also supports braille also – including an on-device keyboard for typing braille so users that wish to type with braille don’t need to connect to their physical braille keyboard.

Additional TalkBack Gestures:

  • Mute/Unmute TalkBack: Three-finger double tap
  • Explore by touch: Drag a finger on the screen to hear the item below your finger spoken
  • Magnification settings: Triple tap
  • Move to the next/previous reading control: 3-finger swipe down/up, respectively

Full gesture list for TalkBack: https://support.google.com/accessibility/android/answer/6151827?hl=en

The use of Screen Readers can greatly benefit your accessibility testing efforts to ensure you are reaching as much of your user base as possible. This empowers users to skip pages, control video, dismiss alerts and even mute the screen reader voice, all with touch gestures! So consider adding Screen Reader support to bolster your accessibility testing efforts.

For more information about Perficient’s Mobile Solutions expertise, subscribe to our blog or contact our Mobile Solutions team today!

]]>
https://blogs.perficient.com/2023/11/16/voiceover-your-talkback-to-me-a-mobile-screen-reader-overview/feed/ 0 349361