Learn Flutter in Minutes

Components vs Widgets

Everything in Flutter is a widget, similar to how everything in React is a component. Both use a declarative UI paradigm.

React

// React functional component
function Greeting({ name }) {
  return (
    <div className="greeting">
      <h1>Hello, {name}!</h1>
      <p>Welcome to React</p>
    </div>
  );
}

// Usage
<Greeting name="Alice" />

Flutter

// Flutter widget
class Greeting extends StatelessWidget {
  final String name;
  
  const Greeting({required this.name});
  
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Text('Hello, $name!'),
        Text('Welcome to Flutter'),
      ],
    );
  }
}

// Usage
Greeting(name: 'Alice')

Key differences

State Management

Flutter's StatefulWidget is similar to React's class components or useState hook.

React

// React with useState hook
function Counter() {
  const [count, setCount] = useState(0);
  
  const increment = () => {
    setCount(count + 1);
  };
  
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={increment}>
        Increment
      </button>
    </div>
  );
}

Flutter

// Flutter StatefulWidget
class Counter extends StatefulWidget {
  @override
  State<Counter> createState() => _CounterState();
}

class _CounterState extends State<Counter> {
  int count = 0;
  
  void increment() {
    setState(() {
      count++;
    });
  }
  
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Text('Count: $count'),
        ElevatedButton(
          onPressed: increment,
          child: Text('Increment'),
        ),
      ],
    );
  }
}

Key differences

Lifecycle Methods

Flutter's lifecycle methods map closely to React hooks and class component lifecycle.

React

// React useEffect hook
function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  
  useEffect(() => {
    // Component mounted
    fetchUser(userId).then(setUser);
    
    return () => {
      // Component will unmount
      cleanup();
    };
  }, [userId]); // Dependencies
  
  return <div>{user?.name}</div>;
}

Flutter

// Flutter lifecycle
class UserProfile extends StatefulWidget {
  final String userId;
  const UserProfile({required this.userId});
  
  @override
  State<UserProfile> createState() => 
    _UserProfileState();
}

class _UserProfileState extends State<UserProfile> {
  User? user;
  
  @override
  void initState() {
    super.initState();
    // Component mounted
    fetchUser(widget.userId).then((u) {
      setState(() => user = u);
    });
  }
  
  @override
  void dispose() {
    // Component will unmount
    cleanup();
    super.dispose();
  }
  
  @override
  Widget build(BuildContext context) {
    return Text(user?.name ?? '');
  }
}

Key differences

Styling & Layout

Flutter uses widget-based styling instead of CSS. Flexbox concepts translate to Row, Column, and Flex.

React

// React with CSS/styled-components
function Card({ title, children }) {
  return (
    <div style={{
      backgroundColor: '#fff',
      borderRadius: '8px',
      padding: '16px',
      boxShadow: '0 2px 4px rgba(0,0,0,0.1)',
      display: 'flex',
      flexDirection: 'column',
      gap: '8px'
    }}>
      <h2 style={{ 
        fontSize: '20px',
        fontWeight: 'bold'
      }}>
        {title}
      </h2>
      {children}
    </div>
  );
}

Flutter

// Flutter with widget styling
class CardWidget extends StatelessWidget {
  final String title;
  final List<Widget> children;
  
  const CardWidget({
    required this.title,
    required this.children,
  });
  
  @override
  Widget build(BuildContext context) {
    return Container(
      decoration: BoxDecoration(
        color: Colors.white,
        borderRadius: BorderRadius.circular(8),
        boxShadow: [
          BoxShadow(
            color: Colors.black.withOpacity(0.1),
            blurRadius: 4,
            offset: Offset(0, 2),
          ),
        ],
      ),
      padding: EdgeInsets.all(16),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text(
            title,
            style: TextStyle(
              fontSize: 20,
              fontWeight: FontWeight.bold,
            ),
          ),
          SizedBox(height: 8),
          ...children,
        ],
      ),
    );
  }
}

Key differences

Lists & Iteration

Both frameworks efficiently render lists of items with keys for optimization.

React

// React list rendering
function TodoList({ todos }) {
  return (
    <ul>
      {todos.map((todo) => (
        <li key={todo.id}>
          {todo.title}
        </li>
      ))}
    </ul>
  );
}

// Optimized list (virtualization)
import { FixedSizeList } from 
  'react-window';

function VirtualList({ items }) {
  return (
    <FixedSizeList
      height={400}
      itemCount={items.length}
      itemSize={50}
    >
      {({ index, style }) => (
        <div style={style}>
          {items[index].title}
        </div>
      )}
    </FixedSizeList>
  );
}

Flutter

// Flutter list rendering
class TodoList extends StatelessWidget {
  final List<Todo> todos;
  
  const TodoList({required this.todos});
  
  @override
  Widget build(BuildContext context) {
    return Column(
      children: todos.map((todo) => 
        Text(todo.title, key: Key(todo.id))
      ).toList(),
    );
  }
}

// Optimized list (built-in virtualization)
class VirtualList extends StatelessWidget {
  final List<Item> items;
  
  const VirtualList({required this.items});
  
  @override
  Widget build(BuildContext context) {
    return ListView.builder(
      itemCount: items.length,
      itemBuilder: (context, index) {
        return ListTile(
          title: Text(items[index].title),
        );
      },
    );
  }
}

Key differences

Navigation & Routing

Flutter's Navigator is similar to React Router, with push/pop semantics for screen navigation.

React

// React Router
import { 
  BrowserRouter,
  Routes,
  Route,
  useNavigate
} from 'react-router-dom';

function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/profile/:id" 
          element={<Profile />} />
      </Routes>
    </BrowserRouter>
  );
}

// Navigation
function Home() {
  const navigate = useNavigate();
  
  const goToProfile = () => {
    navigate('/profile/123');
  };
  
  return (
    <button onClick={goToProfile}>
      View Profile
    </button>
  );
}

Flutter

// Flutter Navigator
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      routes: {
        '/': (context) => HomeScreen(),
        '/profile': (context) => ProfileScreen(),
      },
    );
  }
}

// Navigation
class HomeScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      onPressed: () {
        Navigator.pushNamed(
          context,
          '/profile',
          arguments: {'id': '123'},
        );
      },
      child: Text('View Profile'),
    );
  }
}

// Receiving parameters
class ProfileScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final args = ModalRoute.of(context)!
      .settings.arguments as Map;
    return Text('User ID: ${args['id']}');
  }
}

Key differences

Forms & Input Handling

Both frameworks use controlled components pattern for form inputs.

React

// React controlled input
function LoginForm() {
  const [email, setEmail] = useState('');
  const [password, setPassword] = 
    useState('');
  
  const handleSubmit = (e) => {
    e.preventDefault();
    login(email, password);
  };
  
  return (
    <form onSubmit={handleSubmit}>
      <input
        type="email"
        value={email}
        onChange={(e) => 
          setEmail(e.target.value)
        }
        placeholder="Email"
      />
      <input
        type="password"
        value={password}
        onChange={(e) => 
          setPassword(e.target.value)
        }
        placeholder="Password"
      />
      <button type="submit">
        Login
      </button>
    </form>
  );
}

Flutter

// Flutter form with controllers
class LoginForm extends StatefulWidget {
  @override
  State<LoginForm> createState() => 
    _LoginFormState();
}

class _LoginFormState extends State<LoginForm> {
  final _emailController = 
    TextEditingController();
  final _passwordController = 
    TextEditingController();
  
  void _handleSubmit() {
    login(
      _emailController.text,
      _passwordController.text,
    );
  }
  
  @override
  void dispose() {
    _emailController.dispose();
    _passwordController.dispose();
    super.dispose();
  }
  
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        TextField(
          controller: _emailController,
          decoration: InputDecoration(
            hintText: 'Email',
          ),
        ),
        TextField(
          controller: _passwordController,
          obscureText: true,
          decoration: InputDecoration(
            hintText: 'Password',
          ),
        ),
        ElevatedButton(
          onPressed: _handleSubmit,
          child: Text('Login'),
        ),
      ],
    );
  }
}

Key differences

HTTP Requests & Data Fetching

Both use async/await for API calls. Flutter's http package is similar to fetch or axios.

React

// React with fetch
function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = 
    useState(true);
  const [error, setError] = useState(null);
  
  useEffect(() => {
    fetch(`/api/users/${userId}`)
      .then(res => res.json())
      .then(data => {
        setUser(data);
        setLoading(false);
      })
      .catch(err => {
        setError(err);
        setLoading(false);
      });
  }, [userId]);
  
  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error!</div>;
  
  return <div>{user.name}</div>;
}

Flutter

// Flutter with http package
import 'package:http/http.dart' as http;

class UserProfile extends StatefulWidget {
  final String userId;
  const UserProfile({required this.userId});
  
  @override
  State<UserProfile> createState() => 
    _UserProfileState();
}

class _UserProfileState extends State<UserProfile> {
  User? user;
  bool loading = true;
  String? error;
  
  @override
  void initState() {
    super.initState();
    fetchUser();
  }
  
  Future<void> fetchUser() async {
    try {
      final response = await http.get(
        Uri.parse('/api/users/${widget.userId}')
      );
      final data = jsonDecode(response.body);
      setState(() {
        user = User.fromJson(data);
        loading = false;
      });
    } catch (e) {
      setState(() {
        error = e.toString();
        loading = false;
      });
    }
  }
  
  @override
  Widget build(BuildContext context) {
    if (loading) return CircularProgressIndicator();
    if (error != null) return Text('Error!');
    
    return Text(user!.name);
  }
}

Key differences

Global State Management

Flutter has several state management solutions. Provider is similar to React Context, while Riverpod is like Redux/Zustand.

React

// React Context API
const ThemeContext = createContext();

function App() {
  const [theme, setTheme] = useState('light');
  
  return (
    <ThemeContext.Provider 
      value={{ theme, setTheme }}
    >
      <MainContent />
    </ThemeContext.Provider>
  );
}

// Consuming context
function ThemeToggle() {
  const { theme, setTheme } = 
    useContext(ThemeContext);
  
  return (
    <button onClick={() => 
      setTheme(theme === 'light' 
        ? 'dark' : 'light')
    }>
      Toggle Theme
    </button>
  );
}

Flutter

// Flutter Provider
class ThemeProvider extends ChangeNotifier {
  String _theme = 'light';
  
  String get theme => _theme;
  
  void setTheme(String newTheme) {
    _theme = newTheme;
    notifyListeners();
  }
}

// Setup provider
void main() {
  runApp(
    ChangeNotifierProvider(
      create: (_) => ThemeProvider(),
      child: MyApp(),
    ),
  );
}

// Consuming provider
class ThemeToggle extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final themeProvider = 
      Provider.of<ThemeProvider>(context);
    
    return ElevatedButton(
      onPressed: () {
        themeProvider.setTheme(
          themeProvider.theme == 'light' 
            ? 'dark' : 'light'
        );
      },
      child: Text('Toggle Theme'),
    );
  }
}

Key differences

Animations

Both frameworks have rich animation APIs. Flutter's animations are similar to React Spring or Framer Motion.

React

// React with CSS transitions
function FadeIn({ children }) {
  const [visible, setVisible] = 
    useState(false);
  
  useEffect(() => {
    setVisible(true);
  }, []);
  
  return (
    <div style={{
      opacity: visible ? 1 : 0,
      transition: 'opacity 0.5s ease-in'
    }}>
      {children}
    </div>
  );
}

// With Framer Motion
import { motion } from 'framer-motion';

function AnimatedBox() {
  return (
    <motion.div
      initial={{ opacity: 0, y: 20 }}
      animate={{ opacity: 1, y: 0 }}
      transition={{ duration: 0.5 }}
    >
      Content
    </motion.div>
  );
}

Flutter

// Flutter implicit animations
class FadeIn extends StatefulWidget {
  final Widget child;
  const FadeIn({required this.child});
  
  @override
  State<FadeIn> createState() => 
    _FadeInState();
}

class _FadeInState extends State<FadeIn> {
  bool visible = false;
  
  @override
  void initState() {
    super.initState();
    Future.microtask(() => 
      setState(() => visible = true)
    );
  }
  
  @override
  Widget build(BuildContext context) {
    return AnimatedOpacity(
      opacity: visible ? 1.0 : 0.0,
      duration: Duration(milliseconds: 500),
      curve: Curves.easeIn,
      child: widget.child,
    );
  }
}

// Simpler implicit animation
AnimatedContainer(
  duration: Duration(milliseconds: 500),
  opacity: visible ? 1.0 : 0.0,
  transform: Matrix4.translationValues(
    0, visible ? 0 : 20, 0
  ),
  child: Text('Content'),
)

Key differences


Ready to learn more? Visit the Flutter Documentation or Dart Language Tour.