7 min read Guides Flutter React Native

How to Collect Feature Requests in a Flutter or React Native App

Feature requests reach most small app teams through every channel except the right one: App Store reviews, support emails, a DM on X, a comment under a release post. Each one is a single data point with no context. You can't tell whether ten people want the same thing or one person asked ten times, and the person who asked never hears back.

The fix is to collect requests where your users already are, which is inside your app, and let other users vote on them. This guide sets that up in a Flutter app and in a React Native app, side by side, using FeedbackKit's SDKs. It covers the four things a feature-request flow needs:

  1. A list of existing requests that users can browse and upvote.
  2. A way to submit a new request.
  3. A comment thread on each request.
  4. Statuses, so users can see a request move from pending to in progress to completed, and get an email when that changes.

If you only care about one framework, skip the other's code blocks. For a longer Flutter-only walkthrough of the same widgets, see How to Add a Feedback Board to a Flutter App.

Before you start

You need a FeedbackKit account and a project. The free plan gives you one project and 10 feedback items, which is enough to wire this up and test it. Each project has an API key; you'll pass it to the SDK.

You also need to decide who your users are in FeedbackKit's eyes. Every vote, request and comment is tied to a user ID. If your app has accounts, use your own stable user ID. If it doesn't, generate a random ID on first launch and store it. Don't use something that changes, like a session token, or the same person will be able to vote twice.

Step 1: Install the SDK

Flutter

flutter pub add feedbackkit_flutter

The package supports Flutter 3.10+ and Dart 3.0+.

React Native

npm install feedbackkit-react-native feedbackkit-js @react-native-async-storage/async-storage

The React Native SDK is built on the JavaScript SDK (feedbackkit-js), and uses AsyncStorage to remember the user ID across launches. All three are needed. It supports React Native 0.70+ and React 18+. On a bare React Native project, run pod install in ios/ afterwards so AsyncStorage's native side is linked.

Step 2: Wrap your app in the provider

Both SDKs use a provider at the top of your widget or component tree. It creates the API client and makes it available to every screen below it.

Flutter

import 'package:flutter/material.dart';
import 'package:feedbackkit_flutter/feedbackkit_flutter.dart';

void main() {
  runApp(
    FeedbackKitProvider(
      apiKey: 'your-project-api-key',
      userId: 'user-123', // your stable user ID, if you already know it
      child: const MyApp(),
    ),
  );
}

React Native

import { FeedbackProvider } from 'feedbackkit-react-native';

export default function App() {
  const user = useCurrentUser(); // your own auth hook

  return (
    <FeedbackProvider apiKey="your-project-api-key" userId={user?.id}>
      <NavigationContainer>
        <RootNavigator />
      </NavigationContainer>
    </FeedbackProvider>
  );
}

Put the provider above your MaterialApp or NavigationContainer, so screens you push later can still reach it.

Setting the user after sign-in

If the user signs in after launch, set the ID then. Both SDKs persist it, so it survives restarts.

Flutter

final fk = FeedbackKitProvider.of(context);
await fk.setUserId(user.id);

React Native

import { useFeedbackKitContext } from 'feedbackkit-react-native';

const { setUserId } = useFeedbackKitContext();
setUserId(user.id);

In React Native, voting, submitting and commenting all throw if no user ID is set, so set one before you show the screens below.

Step 3: Show the list of requests

This is the screen users will spend most time on: what other people have asked for, with a vote button on each item.

Flutter

class FeatureRequestsPage extends StatelessWidget {
  const FeatureRequestsPage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Feature requests')),
      body: FeedbackList(
        onFeedbackTap: (item) {
          Navigator.of(context).push(MaterialPageRoute(
            builder: (_) => Scaffold(
              appBar: AppBar(title: Text(item.title)),
              body: FeedbackDetailView(feedback: item),
            ),
          ));
        },
      ),
      floatingActionButton: FloatingActionButton.extended(
        icon: const Icon(Icons.add),
        label: const Text('Request a feature'),
        onPressed: () => Navigator.of(context).push(MaterialPageRoute(
          builder: (_) => const NewRequestPage(),
        )),
      ),
    );
  }
}

FeedbackList loads items and shows a vote button on each card. Votes update optimistically, so the count changes as soon as the user taps. FeedbackDetailView shows the full request with its comment thread. The Flutter list does not include pull-to-refresh.

React Native

import { FeedbackList, FeedbackCategory } from 'feedbackkit-react-native';

function FeatureRequestsScreen({ navigation }) {
  return (
    <FeedbackList
      filterByCategory={FeedbackCategory.FeatureRequest}
      onFeedbackPress={(f) => navigation.navigate('FeatureRequest', { id: f.id })}
      showAddButton
      onAddPress={() => navigation.navigate('NewFeatureRequest')}
    />
  );
}

filterByCategory limits the list to feature requests. Leave it out if you also want bug reports and improvements on the same board. In React Native, FeedbackList supports pull-to-refresh.

Step 4: Let users submit a request

Flutter ships a ready-made form with a title, a description, a category picker and an optional email field:

class NewRequestPage extends StatelessWidget {
  const NewRequestPage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Request a feature')),
      body: SubmitFeedbackView(
        initialCategory: FeedbackCategory.featureRequest,
        onSubmitted: (_) => Navigator.of(context).pop(),
        onCancel: () => Navigator.of(context).pop(),
      ),
    );
  }
}

React Native gives you a hook, and you build the form with your own inputs, so it matches the rest of your app:

import { useState } from 'react';
import { View, TextInput, Button, Text } from 'react-native';
import { useSubmitFeedback, FeedbackCategory } from 'feedbackkit-react-native';

function NewFeatureRequestScreen({ navigation }) {
  const { submit, isSubmitting, error } = useSubmitFeedback();
  const [title, setTitle] = useState('');
  const [description, setDescription] = useState('');
  const [email, setEmail] = useState('');

  const onSubmit = async () => {
    await submit({
      title,
      description,
      category: FeedbackCategory.FeatureRequest,
      userEmail: email || undefined,
    });
    navigation.goBack();
  };

  return (
    <View>
      <TextInput value={title} onChangeText={setTitle} placeholder="What should we build?" />
      <TextInput value={description} onChangeText={setDescription} placeholder="Why do you need it?" multiline />
      <TextInput value={email} onChangeText={setEmail} placeholder="Email (optional, for updates)" />
      <Button title="Submit" onPress={onSubmit} disabled={isSubmitting || !title} />
      {error && <Text>{error.message}</Text>}
    </View>
  );
}

Two small things make a big difference to the quality of what you get. Ask "why do you need it?" in the description placeholder: the reason is often more useful than the request. And make the email optional, but say what it's for. People give it when they know they'll hear back.

Step 5: Comments and voting on a single request

In Flutter, FeedbackDetailView already covers this. In React Native, compose it from hooks and components:

import { View, Text, FlatList } from 'react-native';
import {
  useFeedback,
  useComments,
  VoteButton,
  StatusBadge,
} from 'feedbackkit-react-native';

function FeatureRequestScreen({ route }) {
  const { id } = route.params;
  const { feedback, isLoading } = useFeedback(id);
  const { comments, addComment, isAdding } = useComments(id);

  if (isLoading || !feedback) return null;

  return (
    <View>
      <Text>{feedback.title}</Text>
      <StatusBadge status={feedback.status} />
      <Text>{feedback.description}</Text>
      <VoteButton feedback={feedback} showCount />
      <FlatList
        data={comments}
        keyExtractor={(c) => c.id}
        renderItem={({ item }) => <Text>{item.content}</Text>}
      />
      {/* your own comment input, calling addComment(text) */}
    </View>
  );
}

Comments matter more than they look. A request titled "Export" with five comments explaining what people want to export and where to is something you can actually build.

Step 6: Close the loop with statuses

Every request moves through a status: pending, approved, in progress, TestFlight, completed or rejected. You change it from the FeedbackKit admin app on iPhone, iPad or Mac, or from the web dashboard. Users see the status badge in the list. Once a request is completed or rejected, the vote button is disabled, so a shipped item doesn't keep collecting votes.

The person who submitted a request is emailed when its status changes, on every plan, if they left an email. Choosing which statuses send those emails requires Pro. Voter status emails need the project owner to be on the Team plan. On Free or Pro the vote still counts, and the voter opt-in is not kept. On Team, you can let voters opt in. In React Native, pass their email when they vote. The server stores that opt-in only when the owner is on Team and the vote includes both the address and notifyStatusChange: true:

const { vote } = useVote();
await vote(feedback.id, { email: user.email, notifyStatusChange: true });

The published Flutter SDK cannot make that call. votes.vote takes the feedback ID and an optional notifyOnStatusChange flag, and it does not take an email. Without the address, the server does not store the opt-in. The email on the submit form is what reaches the person who filed the request.

This is the step most teams skip, and it's the one that makes the rest worth doing. When someone gets an email saying "the thing you voted for shipped in this release", they're far more likely to come back and ask for the next thing.

Where to put the entry point

Placement decides how much you collect. Three spots tend to work well:

  • Settings or the account screen: a "Request a feature" row. Low-key and always there.
  • An empty state: if a screen has nothing to show, a link to the board invites ideas at a natural moment.
  • After a release: in your "What's new" screen, link to the board so users can see what shipped and what's next.

Avoid interrupting people with a pop-up. A request board works because people come to it when they have something to say.

Triage on your side

Requests arrive as pending. From the admin apps or the web dashboard you can approve them, change their status, reply, and merge duplicates. Merging combines the votes, so "dark mode", "night theme" and "black background" become one request with an honest vote count. On paid plans you can also push a request into the tool your team already uses: Linear, GitHub, ClickUp, Notion, Slack, Monday.com, Trello, Airtable, Asana or Basecamp. Syncing people to an email list or a CRM — Mailchimp, Kit, beehiiv, MailerLite, HubSpot or Salesforce — is a separate integration.

Start for free

FeedbackKit's free plan includes one project, 10 feedback items, voting, comments and statuses, with no card needed. It's enough to add the board to your app this afternoon and see what your users ask for.

Create your free FeedbackKit project

Keep reading

Collect feedback in your own app

FeedbackKit ships SDKs for Swift, Kotlin, React Native, Flutter and JavaScript, and syncs what your users ask for into the tracker your team already uses.